---
addHowToData: true
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# How to Create an API Route

In this document, you’ll learn how to create API Routes in Medusa.

:::tip

v1.17.2 of `@medusajs/medusa` introduced API Routes to replace Express endpoints. You can still use the [Express endpoints approach](./create-express-route.mdx), however, it's highly recommended that you start using API Routes.

:::

## Basic Implementation

```ts title="src/api/store/custom/route.ts"
import type { 
  MedusaRequest, 
  MedusaResponse,
} from "@medusajs/medusa"

export const GET = (
  req: MedusaRequest,
  res: MedusaResponse
) => {
  res.json({
    message: "[GET] Hello world!",
  })
}

export const POST = (
  req: MedusaRequest, 
  res: MedusaResponse
) => {
  res.json({
    message: "[POST] Hello world!",
  })
}
```

### API Route Path

Custom API Routes must be created in a file named `route.ts` or `route.js` under the `src/api` directory of your Medusa backend or plugin. The API Route's path will be the same as the path of its corresponding `route.ts` file relative to `src/api`.

For example, if you're creating the API route `store/custom`, you must create the file `src/api/store/custom/route.ts`.

### API Route Method

`route.ts` can export at least one of the following method handler functions: `GET`, `POST`, `DELETE`, `PUT`, `PATCH`, `OPTIONS`, and `HEAD`. Defining these method handlers adds a new API Route for the corresponding HTTP method at the same path.

Each of these method handler functions receives two parameters: the `MedusaRequest` which extends Express's [Request](https://expressjs.com/en/api.html#req), and the `MedusaResponse` which extends [Response](https://expressjs.com/en/api.html#res). Both are imported from `@medusajs/medusa`.

In the example above, `GET` and `POST` API Routes will be added at the `store/custom` path.

---

## Building Files

Custom API Routes must be transpiled and moved to the `dist` directory before you can start consuming them. When you run your backend using either the `medusa develop` or `npx medusa develop` commands, it watches the files under `src` for any changes, then triggers the `build` command and restarts the server.

However, the build isn't triggered when the backend first starts running, and it's never triggered when the `medusa start` or `npx medusa start` commands are used.

So, make sure to run the `build` command before starting the backend and testing out your API Routes:

```bash npm2yarn
npm run build
```

---

## Medusa API Routes Path Convention

Although your API Route can be under any path you wish, the Medusa backend uses the following conventions:

- All storefront REST APIs are prefixed by `/store`. For example, the `/store/products` API Route lets you retrieve the products to display them on your storefront.
- All admin REST APIs are prefixed by `/admin`. For example, the `/admin/products` API Route lets you retrieve the products to display them on your admin.

---

## Path Parameters

If your API Route accepts a path parameter, you can place its route file inside a directory with the name `[<PARAMETER_NAME>]`, where `<PARAMETER_NAME>` is the name of your parameter.

For example, to add an API Route at the path `store/custom/[id]`, create the route file at `src/api/store/custom/[id]/route.ts`.

You can access a path parameter's value in method handlers using the `MedusaRequest` object's `params` property, which is an object. Each of the `params` keys is a path parameter's name, and its value is the supplied value when sending the request to the API route.

For example:

```ts title="src/api/store/custom/[id]/route.ts"
import type { 
  MedusaRequest, 
  MedusaResponse,
} from "@medusajs/medusa"

export function GET(
  req: MedusaRequest,
  res: MedusaResponse
) {
  const id = req.params.id

  // do something with the ID.
}
```

An API Route can have more than one path parameter, but each path parameter's nam is unique. If the same path parameter name is used more than once in the same route path, it results in an error and the Medusa Backend won't register the API Route.

For example, if your API route accepts an author ID and a post ID, the path to your route file can be `src/api/author/[id]/posts/[post_id]/route.ts`. You can then use the `MedusaRequest` object's `params.id` and `params.post_id` to access the values of the path parameters.

---

## CORS Configuration

The `cors` middleware, which enables Cross-Origin Resource Sharing (CORS), is automatically applied on custom API Routes defined under the `/store` or `/admin` path prefixes based on the respective [store_cors and admin_cors configurations](../backend/configurations.md#admin_cors-and-store_cors).

To add CORS configurations to custom API routes under other path prefixes, or override the CORS configurations added by default, define a [middleware](./add-middleware.mdx) on your API routes and pass it the `cors` middleware. For example:

```ts title="src/api/middlewares.ts"
import type { 
  MiddlewaresConfig,
} from "@medusajs/medusa"
import cors from "cors"

export const config: MiddlewaresConfig = {
  routes: [
    {
      matcher: "/custom/*",
      middlewares: [
        cors({
          origin: "*",
          credentials: true,
        }),
      ],
    },
  ],
}
```

### CORS Opt-Out

To disable the `cors` middleware for an API Route, export a `CORS` variable in the route file with its value set to `false`.

For example:

```ts title="src/api/store/custom/route.ts"
import type { 
  MedusaRequest, 
  MedusaResponse,
} from "@medusajs/medusa"

export const GET = (
  req: MedusaRequest,
  res: MedusaResponse
) => {
  res.json({
    message: "[GET] Hello world!",
  })
}

export const CORS = false
```

---

## Parse Request Body Parameters

By default, the Medusa backend parses the body of all requests sent to your API Routes with the `Content-Type` header set to `application/json` to a JavaScript object. Then, the parsed data is attached to the `MedusaRequest` object's `body` property, which is an object.

Each of the `body`'s keys are a name of the request body parameters, and its value is the passed value in the request body.

For example:

```ts title="src/api/store/custom/route.ts"
import type { 
  MedusaRequest, 
  MedusaResponse,
} from "@medusajs/medusa"

export const POST = (
  req: MedusaRequest,
  res: MedusaResponse
) => {
  const name = req.body.name

  // do something with the data...
}
```

If you want to parse other content types, such as `application/x-www-form-urlencoded`, you have to add a [middleware](./add-middleware.mdx) to your API routes that parses that body type.

For example:

```ts title="src/api/middlewares.ts"
import type { 
  MiddlewaresConfig,
} from "@medusajs/medusa"
import { 
  urlencoded,
} from "body-parser"

export const config: MiddlewaresConfig = {
  routes: [
    {
      matcher: "/store/*",
      middlewares: [
        urlencoded({ extended: true }),
      ],
    },
  ],
}
```

Note that the `urlencoded` middleware imported from the [body-parser package](https://www.npmjs.com/package/body-parser) attaches the parsed data to the `MedusaRequest` object's `body` property as well.

### Parse Webhook Body Parameters

For webhook API Routes, you may need to use the `raw` body parser middleware rather than the default `json`.

You can opt out of the default body parser by setting the `bodyParser` property of a [middleware route object](./add-middleware.mdx) to `false`, and passing the preferred body-parser middleware in the `middlewares` property.

For example:

```ts title="src/api/middlewares.ts"
import { MiddlewaresConfig } from "@medusajs/medusa"
import { raw } from "body-parser"

export const config: MiddlewaresConfig = {
  routes: [
    {
      matcher: "/webhooks/*",
      bodyParser: false,
      middlewares: [raw({ type: "application/json" })],
    },
  ],
}
```

You can also disable the default `json` body parser for specific HTTP methods using the `method` property for a middleware route object. Its value can either be a string or an array of strings, each being an HTTP method name.

For example:

```ts title="src/api/middlewares.ts"
import { MiddlewaresConfig } from "@medusajs/medusa"
import { raw } from "body-parser"

export const config: MiddlewaresConfig = {
  routes: [
    {
      method: ["POST", "PUT"],
      matcher: "/webhooks/*",
      bodyParser: false,
      middlewares: [raw({ type: "application/json" })],
    },
  ],
}
```

This disables the default JSON body parser for the POST and PUT HTTP methods on API Routes matching the `/webhooks/*` matcher, and applies the `raw` body-parser middleware on them instead.

### Configure Request Body Size Limit

By default, the maximum request body size allowed is `1000` bytes. If a request body's size is greater than that, an error is thrown.

If you expect the request body of an API Route to be larger than the default, you can set the `bodyParser` property of a [middleware route object](./add-middleware.mdx) to a configuration object with the property `sizeLimit`. Its value is a number indicating the maximum allowed size limit in bytes.

For example:

```ts title="src/api/middlewares.ts"
import { MiddlewaresConfig } from "@medusajs/medusa"

export const config: MiddlewaresConfig = {
  routes: [
    {
      bodyParser: { sizeLimit: 2000 }, // in bytes
      // ...
    },
  ],
}
```

---

## Protected API Routes

Protected API routes are routes that should only be accessible by logged-in customers or users.

### Protect Store API Routes

By default, API routes prefixed by `/store` don't require customer authentication to access the API route. However, you can still access the logged-in customer's ID in the API Route method handler using the `MedusaRequest` object's `user.customer_id`, which will be `undefined` if the customer isn't logged in.

For example:

```ts title="src/api/store/custom/route.ts"
import { CustomerService } from "@medusajs/medusa"
import type { 
  MedusaRequest, 
  MedusaResponse,
} from "@medusajs/medusa"

export const GET = async (
  req: MedusaRequest,
  res: MedusaResponse
) => {
  const id = req.user.customer_id
  
  if (!id) {
    // TODO handle not logged in
    // customers based on the custom
    // API route's functionality
  }

  const customerService = req.scope.resolve<CustomerService>(
    "customerService"
  )
      
  const customer = await customerService.retrieve(id)

  // ...
}
```

API Routes prefixed by `/store/me`, on the other hand, require customer authentication to access the API Route. You can access the logged-in customer's ID in the API Route method handler using the `MedusaRequest` object's `user.customer_id`.

If you want to disable authentication requirement on your custom API Route prefixed with `/store/me`, export an `AUTHENTICATE` variable in the route file with its value set to `false`. For example:

```ts title="src/api/store/me/custom/route.ts"
import type { 
  MedusaRequest, 
  MedusaResponse,
} from "@medusajs/medusa"

export const GET = async (
  req: MedusaRequest,
  res: MedusaResponse
) => {
  res.json({
    message: "Hello",
  })
}

export const AUTHENTICATE = false
```

:::note

This disables authentication requirement on all API Route methods defined in the same file.

:::

### Protect Admin API Routes

By default, all API Routes prefixed by `/admin` require admin user authentication to access the API Route. You can access the logged-in user's ID in the API Route method handler using the `MedusaRequest` object's `user.userId`.

For example:

```ts title="src/api/admin/custom/route.ts"
import type { 
  MedusaRequest, 
  MedusaResponse,
  UserService,
} from "@medusajs/medusa"

export const GET = async (
  req: MedusaRequest,
  res: MedusaResponse
) => {
  const id = req.user.userId

  const userService = req.scope.resolve<UserService>(
    "userService"
  )
      
  const user = await userService.retrieve(id)

  // ...
}
```

To disable authentication requirement on an admin API Route, export an `AUTHENTICATE` variable in your route file with its value set to `false`.

For example:

```ts title="src/api/admin/custom/route.ts"
import type { 
  MedusaRequest, 
  MedusaResponse,
} from "@medusajs/medusa"

export const GET = async (
  req: MedusaRequest,
  res: MedusaResponse
) => {
  res.json({
    message: "Hello",
  })
}

export const AUTHENTICATE = false
```

:::note

This disables authentication requirement on all API Route methods defined in the same file.

:::

### Protect Other API Routes

To protect API routes that aren't prefixed with `/store` or `/admin`, you can use one of the following middlewares exported by `@medusajs/medusa` for authenticating customers or users:

- `authenticate`: this middleware ensures that only authenticated admin users can access an API Route. You can access the user's ID in the API Route method handler using the `MedusaRequest` object's `user.userId`. 
- `authenticateCustomer`: this middleware doesn't require a customer to be authenticated, but if a customer is logged in, it attaches their ID to the `MedusaRequest` object's `user.customer_id`.
- `requireCustomerAuthentication`: this middleware ensures that only authenticated customers can access an API Route. You can access the customer's ID in the API Route method handler using the `MedusaRequest` object's `user.customer_id`.

For example:

```ts title="src/api/middlewares.ts"
import { 
  authenticate, 
  requireCustomerAuthentication, 
  type MiddlewaresConfig,
} from "@medusajs/medusa"

export const config: MiddlewaresConfig = {
  routes: [
    {
      matcher: "/custom/admin*",
      middlewares: [authenticate()],
    },
    {
      matcher: "/custom/customer*",
      middlewares: [requireCustomerAuthentication()],
    },
  ],
}
```

---

## Retrieve Medusa Config

You can access the configurations exported in `medusa-config.js`, including your custom configurations, by resolving the `configModule` resource using [dependency injection](../fundamentals/dependency-injection.md).

For example:

```ts title="src/api/store/custom/route.ts"
import type { 
  MedusaRequest,
  MedusaResponse,
} from "@medusajs/medusa"
import { ConfigModule } from "@medusajs/medusa"

// This is only helpful if you're 
// accessing custom configurations
// otherwise it's fine to just use `ConfigModule`
type MyConfigModule = ConfigModule & {
  projectConfig: {
    custom_config?: string
  }
}

export const GET = (
  req: MedusaRequest,
  res: MedusaResponse
) => {
  const configModule = req.scope.resolve<MyConfigModule>(
    "configModule"
  )
  res.json({
    message: configModule.projectConfig.custom_config,
  })
}
```

---

## Handle Errors

Medusa automatically applies an error-handler middleware on your custom API routes, which returns errors as a JSON response whose format is consistent with the Medusa backend.

:::tip

When an error is thrown, the response status code is set to `500` by default. However, that changes based on the `MedusaError` type thrown as explained in the next section.

:::

### Medusa Error Types

You must throw errors of type `MedusaError` to ensure the error message is returned in the response. Otherwise, the returned error message will be `Unknown Error`.

For example, if you throw an error like this:

```ts
throw new Error ("Post was not found")
```

The API Route returns the following object error in the response:

```json
{
  "code": "unknown_error",
  "type": "unknown_error",
  "message": "An unknown error occurred."
}
```

To ensure your error message is relayed in the response, use `MedusaError` imported from `@medusajs/utils` as the thrown error type instead.

For example:

```ts
import { MedusaError } from "@medusajs/utils"

// ...

throw new MedusaError(
  MedusaError.Types.NOT_FOUND,
  "Post was not found"
)
```

The constructor of `MedusaError` accepts the following parameters:

1. The first parameter is the error's type. You can use one of the predefined errors under `MedusaError.Types`, such as `MedusaError.Types.NOT_FOUND` which sets the response status code to `404` automatically.
2. The second parameter is the message of the error.
3. The third parameter is an optional code, which is a string, that's returned in the error object.

After using `MedusaError`, the returned error in the response provides a clearer message:

```json
{
  "type": "not_found",
  "message": "Post was not found"
}
```

<Details>
  <Summary>Available MedusaError Types and their respective status codes</Summary>

  The default response code is `500` unless mentioned otherwise.

  - `MedusaError.Types.DB_ERROR`: Sets the response code to `500`.
  - `MedusaError.Types.DUPLICATE_ERROR`: Sets the response code to `422`.
  - `MedusaError.Types.INVALID_ARGUMENT`
  - `MedusaError.Types.INVALID_DATA`: Sets the response code to `400`.
  - `MedusaError.Types.UNAUTHORIZED`: Sets the response code to `401`.
  - `MedusaError.Types.NOT_FOUND`: Sets the response code to `404`.
  - `MedusaError.Types.NOT_ALLOWED`: Sets the response code to `400`.
  - `MedusaError.Types.UNEXPECTED_STATE`
  - `MedusaError.Types.CONFLICT`: Sets the response code to `409`.
  - `MedusaError.Types.PAYMENT_AUTHORIZATION_ERROR`: Sets the response code to `422`.

</Details>

### Override Error Handler

To override the default error handler, pass the `errorHandler` property to the [exported middleware configurations](./add-middleware.mdx) with the custom error-handler middleware as its value.

For example:

```ts title="src/api/middlewares.ts"
import { MiddlewaresConfig } from "@medusajs/medusa"

export const config: MiddlewaresConfig = {
  errorHandler: (err, req, res, next) => {
    // custom error handling logic...
  },
}
```

### Disable Default Error Handler

To disable the default error handler, set the `errorHandler` property of the [exported middleware configurations](./add-middleware.mdx) to `false`.

For example:

```ts title="src/api/middlewares.ts"
import { MiddlewaresConfig } from "@medusajs/medusa"

export const config: MiddlewaresConfig = {
  errorHandler: false,
}
```

However, when you disable the default error handler, errors thrown in an `async` function or method are not handled and requests goes on indefinitely with no response.

To ensure that errors are still returned in the response when the default error handler is disabled, either create a custom error handler or wrap your API Route with the `wrapHandler` imported from `@medusajs/medusa`.

For example:

```ts title="src/api/middlewares.ts"
import { 
  MedusaRequest, 
  MedusaResponse, 
  wrapHandler,
} from "@medusajs/medusa"

export const GET = wrapHandler(async (
  req: MedusaRequest,
  res: MedusaResponse
): Promise<void> => {
  throw new Error("An error occurred")
})

```

---

## Use Other Resources

Resources, such as services, that are registered in the [dependency container](../fundamentals/dependency-injection.md) can be retrieved in an API Route's handler method using the `MedusaRequest` object's `scope.resolve` method.

The `scope` method accepts as a parameter the resource's registration name in the [dependency container](../fundamentals/dependency-injection.md).

### Example: Retrieve Repository

:::tip

Posts are represented by a custom entity not covered in this guide. You can refer to the [entities](../entities/create.mdx#adding-relations) for more details on how to create a custom entity.

:::

```ts title="src/api/store/posts/route.ts"
import type { 
  MedusaRequest, 
  MedusaResponse,
} from "@medusajs/medusa"
import { 
  PostRepository,
} from "../../../repositories/post"
import { EntityManager } from "typeorm"

export const GET = async (
  req: MedusaRequest, 
  res: MedusaResponse
) => {
  const postRepository = 
    req.scope.resolve<PostRepository>("postRepository")
  const manager = req.scope.resolve<EntityManager>("manager")
  const postRepo = manager.withRepository(postRepository)

  res.json({
    posts: await postRepo.find(),
  })
}
```

Notice that to retrieve an instance of the repository, you need to retrieve first Typeorm's Entity Manager from the dependency container, then use its `withRepository` method.

### Example: Retrieve Service

:::note

`PostService` is a custom service that is not covered in this guide. You can refer to the [services](../services/create-service.mdx) documentation for more details on how to create a custom service, and find an [example of PostService](../services/create-service.mdx#example-services-with-crud-operations)

:::

```ts title="src/api/store/posts/route.ts"
import type { 
  MedusaRequest, 
  MedusaResponse,
} from "@medusajs/medusa"
import { PostService } from "../../../services/post"

export const GET = async (
  req: MedusaRequest, 
  res: MedusaResponse
) => {
  const postService: PostService = req.scope.resolve(
    "postService"
  )

  res.json({
    posts: await postService.list(),
  })
}
```

---

## Ignored Files and Directories

Files and directories prefixed with `_` are ignored. This can be helpful if you want to implement API Route method handlers in different files, then reference them in your `route.ts` file.

For example:

<Tabs groupId="files" isCodeTabs={true}>
  <TabItem value="custom-route" label="src/api/custom/route.ts" default>

    ```ts
    import getProducts from "../_methods/get-products"

    export const GET = getProducts
    ```

  </TabItem>
  <TabItem value="internal-method" label="src/api/_methods/get-product.ts">

    ```ts
    import { 
      MedusaRequest, 
      MedusaResponse, 
      ProductService,
    } from "@medusajs/medusa"

    export default async function (
      req: MedusaRequest, 
      res: MedusaResponse
    ) {
      const productService = req.scope.resolve<ProductService>(
        "productService"
      )

      const products = await productService.list({})

      res.json({
        products,
      })
    }
    ```

  </TabItem>
</Tabs>

---

## Example: CRUD API Routes

This section provides an example of creating API Routes that perform Create, Read, Update, and Delete (CRUD) operations.

:::note

You can refer to the [Entities](../entities/create.mdx#adding-relations) and [Services](../services/create-service.mdx#example-services-with-crud-operations) documentation to learn how to create the custom entities and services used in this example.

:::

<Tabs groupId="files" isCodeTabs={true}>
  <TabItem value="posts-routes" label="src/api/admin/posts/route.ts" default>

    ```ts
    import type {
      MedusaRequest, 
      MedusaResponse,
    } from "@medusajs/medusa"
    import { PostService } from "../../../services/post"

    // list posts
    export const GET = async (
      req: MedusaRequest, 
      res: MedusaResponse
    ) => {
      const postService: PostService = req.scope.resolve(
        "postService"
      )

      res.json({
        posts: await postService.list(),
      })
    }

    // create a post
    export const POST = async (
      req: MedusaRequest, 
      res: MedusaResponse
    ) => {
      const postService: PostService = req.scope.resolve(
        "postService"
      )

      // basic validation of request body
      if (!req.body.title || !req.body.author_id) {
        throw new Error("`title` and `author_id` are required.")
      }

      const post = await postService.create(req.body)

      res.json({
        post,
      })
    }
    ```

  </TabItem>
  <TabItem value="posts-id-routes" label="src/api/admin/posts/[id]/route.ts">

    ```ts
    import type { 
      MedusaRequest, 
      MedusaResponse,
    } from "@medusajs/medusa"
    import { PostService } from "../../../services/post"

    // retrieve a post by its ID
    export const GET = async (
      req: MedusaRequest, 
      res: MedusaResponse
    ) => {
      const postService: PostService = req.scope.resolve(
        "postService"
      )

      const post = await postService.retrieve(req.params.id)

      res.json({
        post,
      })
    }

    // update a post by its ID
    export const POST = async (
      req: MedusaRequest, 
      res: MedusaResponse
    ) => {
      const postService: PostService = req.scope.resolve(
        "postService"
      )

      // basic validation of request body
      if (req.body.id) {
        throw new Error("Can't update post ID")
      }

      const post = await postService.update(
        req.params.id,
        req.body
      )

      res.json({
        post,
      })
    }

    // delete a post by its ID
    export const DELETE = async (
      req: MedusaRequest, 
      res: MedusaResponse
    ) => {
      const postService: PostService = req.scope.resolve(
        "postService"
      )

      await postService.delete(req.params.id)

      res.status(200).end()
    }
    ```

  </TabItem>
</Tabs>

---

## See Also

- [Storefront API Reference](https://docs.medusajs.com/api/store)
- [Admin API Reference](https://docs.medusajs.com/api/admin)
