Documentation Index Fetch the complete documentation index at: https://mintlify.com/medusajs/medusa/llms.txt
Use this file to discover all available pages before exploring further.
API routes in Medusa are file-based HTTP endpoints that follow Next.js-style conventions. They automatically integrate with authentication, validation, and the dependency injection container.
Route Basics
Routes are defined in the src/api directory with a specific file structure:
src/api/
├── admin/ # Admin API routes (requires authentication)
│ └── brands/
│ ├── route.ts # /admin/brands
│ └── [id]/
│ └── route.ts # /admin/brands/:id
└── store/ # Store API routes (public or customer auth)
└── brands/
└── route.ts
Creating a Basic Route
Create the Route File
Create a route.ts file with named exports for HTTP methods: src/api/admin/brands/route.ts
import {
AuthenticatedMedusaRequest ,
MedusaResponse ,
} from "@medusajs/framework/http"
export async function GET (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
res . json ({
brands: [],
})
}
export async function POST (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
res . json ({
brand: { id: "brand_123" },
})
}
Available HTTP methods: GET, POST, PUT, PATCH, DELETE
Use Dependency Injection
Access services and modules via req.scope: import { BRAND_MODULE } from "../../../modules/brand"
import { IBrandModuleService } from "../../../modules/brand/types"
export async function GET (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
const brandService = req . scope . resolve < IBrandModuleService >(
BRAND_MODULE
)
const brands = await brandService . listBrands ()
res . json ({ brands })
}
Execute Workflows
Most routes should execute workflows instead of calling services directly: import { createBrandWorkflow } from "../../../workflows/brand/create-brand"
export async function POST (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
const { result : brand } = await createBrandWorkflow ( req . scope ). run ({
input: req . body ,
})
res . status ( 201 ). json ({ brand })
}
Complete CRUD Example
Here’s a full example implementing CRUD operations:
List Brands
src/api/admin/brands/route.ts
import {
AuthenticatedMedusaRequest ,
MedusaResponse ,
} from "@medusajs/framework/http"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
export async function GET (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
const query = req . scope . resolve ( ContainerRegistrationKeys . QUERY )
const { data : brands , metadata } = await query . graph ({
entity: "brand" ,
fields: req . queryConfig . fields ,
filters: req . filterableFields ,
pagination: req . queryConfig . pagination ,
})
res . json ({
brands ,
count: metadata . count ,
offset: metadata . skip ,
limit: metadata . take ,
})
}
Create Brand
src/api/admin/brands/route.ts
import { createBrandWorkflow } from "../../../workflows/brand/create-brand"
export async function POST (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
const { result : brand } = await createBrandWorkflow ( req . scope ). run ({
input: {
name: req . validatedBody . name ,
description: req . validatedBody . description ,
},
})
res . status ( 201 ). json ({ brand })
}
Get Single Brand
src/api/admin/brands/[id]/route.ts
import {
AuthenticatedMedusaRequest ,
MedusaResponse ,
} from "@medusajs/framework/http"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
export async function GET (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
const query = req . scope . resolve ( ContainerRegistrationKeys . QUERY )
const { data : brands } = await query . graph ({
entity: "brand" ,
filters: { id: req . params . id },
fields: req . queryConfig . fields ,
})
if ( ! brands . length ) {
return res . status ( 404 ). json ({
message: `Brand with id ${ req . params . id } not found` ,
})
}
res . json ({ brand: brands [ 0 ] })
}
Update Brand
src/api/admin/brands/[id]/route.ts
import { updateBrandWorkflow } from "../../../../workflows/brand/update-brand"
export async function POST (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
const { result : brand } = await updateBrandWorkflow ( req . scope ). run ({
input: {
id: req . params . id ,
... req . validatedBody ,
},
})
res . json ({ brand })
}
Delete Brand
src/api/admin/brands/[id]/route.ts
import { deleteBrandWorkflow } from "../../../../workflows/brand/delete-brand"
export async function DELETE (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
await deleteBrandWorkflow ( req . scope ). run ({
input: { ids: [ req . params . id ] },
})
res . json ({
id: req . params . id ,
object: "brand" ,
deleted: true ,
})
}
Request Types
AuthenticatedMedusaRequest
Use for admin routes that require authentication:
import { AuthenticatedMedusaRequest } from "@medusajs/framework/http"
export async function GET (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
// Access authenticated user
const userId = req . auth_context . actor_id
// User is guaranteed to be authenticated
}
MedusaRequest
Use for public routes that don’t require authentication:
import { MedusaRequest , MedusaResponse } from "@medusajs/framework/http"
export async function GET (
req : MedusaRequest ,
res : MedusaResponse
) {
// No authentication required
}
Type Parameters
Add type parameters for request body and query params:
import { AuthenticatedMedusaRequest } from "@medusajs/framework/http"
import type { HttpTypes } from "@medusajs/framework/types"
interface CreateBrandBody {
name : string
description ?: string
}
interface BrandQueryParams {
fields ?: string
}
export async function POST (
req : AuthenticatedMedusaRequest < CreateBrandBody , BrandQueryParams >,
res : MedusaResponse < HttpTypes . AdminBrandResponse >
) {
const { name , description } = req . validatedBody
// Type-safe access to body
}
Request Properties
req.scope
Dependency injection container:
const query = req . scope . resolve ( ContainerRegistrationKeys . QUERY )
const logger = req . scope . resolve ( ContainerRegistrationKeys . LOGGER )
const brandService = req . scope . resolve ( BRAND_MODULE )
req.params
URL path parameters:
// Route: /admin/brands/[id]/route.ts
// URL: /admin/brands/brand_123
const brandId = req . params . id // "brand_123"
req.validatedBody
Validated request body (when using validators):
const { name , description } = req . validatedBody
req.validatedQuery
Validated query parameters:
const { limit , offset } = req . validatedQuery
req.filterableFields
Filters for querying:
const brands = await query . graph ({
entity: "brand" ,
filters: req . filterableFields ,
})
req.queryConfig
Query configuration:
const { fields , pagination } = req . queryConfig
req.auth_context
Authentication context (on AuthenticatedMedusaRequest):
const userId = req . auth_context . actor_id
const authType = req . auth_context . auth_identity_id
Response Methods
res.json()
Send JSON response:
res . json ({ brand: { id: "brand_123" } })
res.status()
Set status code:
res . status ( 201 ). json ({ brand })
res . status ( 404 ). json ({ message: "Not found" })
res . status ( 204 ). send ()
Using Query for Data Fetching
The Query service provides a powerful way to fetch related data:
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
export async function GET (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
const query = req . scope . resolve ( ContainerRegistrationKeys . QUERY )
const { data : brands } = await query . graph ({
entity: "brand" ,
fields: [
"id" ,
"name" ,
"description" ,
"products.*" ,
"products.variants.*" ,
],
filters: {
name: { $like: "%Nike%" },
},
pagination: {
skip: 0 ,
take: 20 ,
},
})
res . json ({ brands })
}
Error Handling
Use standard HTTP status codes and error responses:
import { MedusaError } from "@medusajs/framework/utils"
export async function GET (
req : AuthenticatedMedusaRequest ,
res : MedusaResponse
) {
const query = req . scope . resolve ( ContainerRegistrationKeys . QUERY )
const { data : brands } = await query . graph ({
entity: "brand" ,
filters: { id: req . params . id },
})
if ( ! brands . length ) {
throw new MedusaError (
MedusaError . Types . NOT_FOUND ,
`Brand with id ${ req . params . id } was not found`
)
}
res . json ({ brand: brands [ 0 ] })
}
Best Practices
Use AuthenticatedMedusaRequest for admin routes requiring authentication
Execute workflows instead of calling services directly
Use the Query service for complex data fetching
Type your request and response for type safety
Return appropriate HTTP status codes (200, 201, 204, 404, etc.)
Use MedusaError for consistent error handling
Keep route handlers thin - business logic belongs in workflows
Access services via req.scope.resolve()
Next Steps
Create Workflows Build business logic for your routes
Create Services Implement module services