> ## 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.

# Create API Routes

> Learn how to create custom API routes in Medusa with AuthenticatedMedusaRequest and MedusaResponse.

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

<Steps>
  <Step title="Create the Route File">
    Create a `route.ts` file with named exports for HTTP methods:

    ```typescript title="src/api/admin/brands/route.ts" theme={null}
    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`
  </Step>

  <Step title="Use Dependency Injection">
    Access services and modules via `req.scope`:

    ```typescript theme={null}
    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 })
    }
    ```
  </Step>

  <Step title="Execute Workflows">
    Most routes should execute workflows instead of calling services directly:

    ```typescript theme={null}
    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 })
    }
    ```
  </Step>
</Steps>

## Complete CRUD Example

Here's a full example implementing CRUD operations:

### List Brands

```typescript title="src/api/admin/brands/route.ts" theme={null}
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

```typescript title="src/api/admin/brands/route.ts" theme={null}
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

```typescript title="src/api/admin/brands/[id]/route.ts" theme={null}
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

```typescript title="src/api/admin/brands/[id]/route.ts" theme={null}
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

```typescript title="src/api/admin/brands/[id]/route.ts" theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
// 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):

```typescript theme={null}
const { name, description } = req.validatedBody
```

### req.validatedQuery

Validated query parameters:

```typescript theme={null}
const { limit, offset } = req.validatedQuery
```

### req.filterableFields

Filters for querying:

```typescript theme={null}
const brands = await query.graph({
  entity: "brand",
  filters: req.filterableFields,
})
```

### req.queryConfig

Query configuration:

```typescript theme={null}
const { fields, pagination } = req.queryConfig
```

### req.auth\_context

Authentication context (on `AuthenticatedMedusaRequest`):

```typescript theme={null}
const userId = req.auth_context.actor_id
const authType = req.auth_context.auth_identity_id
```

## Response Methods

### res.json()

Send JSON response:

```typescript theme={null}
res.json({ brand: { id: "brand_123" } })
```

### res.status()

Set status code:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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

<CardGroup cols={2}>
  <Card title="Create Workflows" icon="diagram-project" href="/development/workflows">
    Build business logic for your routes
  </Card>

  <Card title="Create Services" icon="gear" href="/development/services">
    Implement module services
  </Card>
</CardGroup>
