# Checkpoint 1.3: Brand API Route
This checkpoint verifies that you've successfully created the POST /admin/brands API route with validation and middleware.
## Verification Questions
Before proceeding, test your understanding:
1. **Why do we execute workflows from API routes instead of calling services directly?**
Answer
Workflows provide orchestration, rollback, and transaction management. If you call services directly from routes, you have to manually handle rollback logic when errors occur. Workflows handle this automatically through compensation functions. This becomes crucial as your business logic grows more complex with multiple steps.
2. **What does `validateAndTransformBody` middleware do?**
Answer
It validates incoming request body against a Zod schema BEFORE your route handler runs. If validation fails, it automatically returns a 400 error with validation details. If validation succeeds, it transforms the data according to the schema and passes the validated data to your handler. This ensures your handler only receives valid data.
3. **Why do we use `MedusaRequest` and `MedusaResponse` instead of Express types?**
Answer
These are Medusa-specific types that extend Express types with additional properties like `scope` (for dependency injection) and `queryConfig` (for filtering/pagination). Using these types gives you type-safe access to Medusa-specific features.
4. **What is the `scope` object and how does it work?**
Answer
`scope` is Medusa's dependency injection container scoped to the current request. You pass it to workflows when executing them (e.g., `workflow(req.scope).run()`), and use it to resolve services (e.g., `scope.resolve("query")`). Each request gets its own scope, ensuring proper isolation and allowing request-specific configuration.
## Implementation Check
Let me verify your implementation. Please share the following:
### 1. Schema File
Show me your `src/api/admin/brands/validators.ts` file.
**Key things to check**:
- [ ] Imports `z` from "@medusajs/framework/zod"
- [ ] Defines `CreateBrandSchema` with `z.object()`
- [ ] Has `name` field with `z.string()`
- [ ] Exports schema as named export
### 2. Route File
Show me your `src/api/admin/brands/route.ts` file.
**Key things to check**:
- [ ] Imports types: `MedusaRequest`, `MedusaResponse`
- [ ] Imports workflow: `import { createBrandWorkflow } from "..."`
- [ ] Imports workflow input type: `CreateBrandWorkflowInput`
- [ ] Defines `POST` function (must be named `POST` exactly)
- [ ] Uses type: `MedusaRequest`
- [ ] Executes workflow: `await createBrandWorkflow(req.scope).run({ input: ... })`
- [ ] Extracts brand from result: `result.result` or `result.brand`
- [ ] Returns JSON: `res.json({ brand })`
- [ ] Handles errors with try/catch
### 3. Middleware File
Show me your `src/api/middlewares.ts` file.
**Key things to check**:
- [ ] Imports `defineMiddlewares`, `validateAndTransformBody`
- [ ] Imports `CreateBrandSchema`
- [ ] Exports `default defineMiddlewares()`
- [ ] Has `routes` array
- [ ] Route config has `matcher: "/admin/brands"`
- [ ] Route config has `method: "POST"`
- [ ] Route config has `middlewares` array with `validateAndTransformBody()`
### 4. Server Running
Start your dev server:
```bash
npm run dev
```
**Expected output**: Server should start without errors. Check that there are no errors about missing routes or middleware.
## Common Issues
### Middleware not running / validation not working
**Symptom**: Invalid data passes through without validation errors
**Cause**: Middleware not configured correctly
**Fix**:
1. Check that `matcher` exactly matches your route: `"/admin/brands"`
2. Check that `method` is uppercase: `"POST"`
3. Ensure `middlewares.ts` is in the correct location: `src/api/middlewares.ts`
4. Restart dev server after middleware changes
### "Empty array returned" or "brand is undefined"
**Symptom**: API returns empty response or undefined brand
**Cause**: Not extracting brand from workflow result correctly
**Fix**:
Workflow results are nested:
```typescript
const { result } = await workflow.run({ input: req.validatedBody })
const brand = result.result // Note: double .result
res.json({ brand })
```
The first `.result` is the workflow execution result, the second `.result` is from `WorkflowResponse(brand)`.
### Route not found / 404 error
**Symptom**: cURL returns 404
**Cause**: File not in correct location or not named correctly
**Fix**:
1. Ensure file is at: `src/api/admin/brands/route.ts`
2. Ensure function is exported as `POST` (not default export)
3. Restart dev server
4. Check URL is correct: `http://localhost:9000/admin/brands`
### "Workflow failed" with no specific error
**Symptom**: Generic workflow failure
**Cause**: Error in step execution (likely in createBrandStep)
**Fix**:
1. Check server logs for detailed error message
2. Verify brand service is accessible in the step
3. Verify database connection is working
4. Check that migrations ran successfully
### TypeScript error: "Property 'validatedBody' does not exist"
**Symptom**: Build fails with TS error
**Cause**: Missing type for validated body
**Fix**:
Use generic type parameter:
```typescript
export const POST = async (
req: MedusaRequest,
res: MedusaResponse
) => {
const input = req.validatedBody // TypeScript knows this is CreateBrandWorkflowInput
}
```
## Testing Checklist
Verify each of these steps:
- [ ] Server starts without errors
- [ ] POST request to `/admin/brands` succeeds
- [ ] Response contains brand object with id and name
- [ ] Invalid request (missing name) returns 400 error
- [ ] Brand is actually saved (check with GET request or database query)
- [ ] Build succeeds: `npm run build`
## Manual Database Verification (Optional)
If you want to verify the brand was actually saved:
```bash
# Connect to your database
psql your_database_name
# Query brands table
SELECT * FROM brand;
```
You should see the Nike brand you created.
## Architecture Understanding
At this point, you should understand the full three-layer pattern:
```
┌─────────────────────────────────────────────────┐
│ API Route (HTTP Interface) │
│ - Validates input │
│ - Executes workflow │
│ - Returns response │
└─────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Workflow (Business Logic Orchestration) │
│ - Coordinates steps │
│ - Handles rollback │
│ - Manages transactions │
└─────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ Module (Data Layer) │
│ - Provides CRUD operations │
│ - Isolated from other modules │
└─────────────────────────────────────────────────┘
```
**Why this matters**:
- **Separation of concerns**: Each layer has a single responsibility
- **Reusability**: Workflow can be called from multiple routes (HTTP, GraphQL, CLI)
- **Testability**: Each layer can be tested independently
- **Maintainability**: Changes to one layer don't affect others
## Next Steps
Once this checkpoint passes:
1. **Lesson 1 Complete!** You've built a complete feature from scratch:
- Brand Module (data layer)
- createBrandWorkflow (business logic with rollback)
- POST /admin/brands (HTTP interface with validation)
2. **Commit your work**:
```bash
git add .
git commit -m "Complete Lesson 1: Build custom brand feature"
```
3. **Next: Lesson 2** - Extend Medusa
- Link brands to products using Module Links
- Extend core workflows using Workflow Hooks
- Query linked data across modules
**Ready for Lesson 2?** This is where it gets really interesting - you'll learn how to extend Medusa's core functionality without forking the codebase.