docs: update endpoints to use file-routing approach (#5397)

- Move the original guides for creating endpoints and middlewares to sub-sections in the Endpoints category.
- Replace existing guides for endpoints and middlewares with the new approach.
- Update all endpoints-related snippets across docs to use this new approach.
This commit is contained in:
Shahed Nasser authored and GitHub committed 2023-10-19 15:56:26 +00:00
1 parent b38f73726d
commit c28935b4e8
170 files changed
+3658 -3344

No files matched your search

@@ -0,0 +1,221 @@
---
addHowToData: true
---
import Troubleshooting from '@site/src/components/Troubleshooting'
import ServiceLifetimeSection from '../../troubleshooting/awilix-resolution-error/_service-lifetime.md'
import CustomRegistrationSection from '../../troubleshooting/awilix-resolution-error/_custom-registration.md'
# Middlewares - Express Endpoints Approach
In this document, you’ll learn how to add a middleware to an existing or custom route in Medusa.
:::note
Following v1.17.2 of `@medusajs/medusa`, it's highly recommended to use the [middlewares.ts file](./add-middleware.mdx) to create middlewares instead. Future versions of Medusa may drop support of this approach.
:::
## Overview
As the Medusa backend is built on top of [Express](https://expressjs.com/), Express’s features can be utilized during your development with Medusa.
One feature in particular is adding a [middleware](http://expressjs.com/en/guide/using-middleware.html#using-middleware). A middleware is a function that has access to the request and response objects.
A middleware can be used to perform an action when an endpoint is called or modify the response, among other usages.
You can add a middleware to an existing route in the Medusa backend, a route in a plugin, or your custom routes.
---
## How to Add a Middleware
### Step 1: Create the Middleware File
You can organize your middlewares as you see fit, but it's recommended to create Middlewares in the `src/api/middlewares` directory. It's recommended to create each middleware in a different file.
Each file should export a middleware function that accepts three parameters:
1. The first one is an Express request object. It can be used to get details related to the request or resolve resources from the dependency container.
2. The second one is an Express response object. It can be used to modify the response, or in some cases return a response without executing the associated endpoint.
3. The third one is a next middleware function that ensures that other middlewares and the associated endpoint are executed.
:::info
You can learn more about Middlewares and their capabilities in [Express’s documentation](http://expressjs.com/en/guide/using-middleware.html#using-middleware).
:::
Here's an example of a middleware:
```ts title=src/api/middlewares/custom-middleware.ts
export function customMiddleware(req, res, next) {
// TODO perform an action
next()
}
```
### Step 2: Apply Middleware on an Endpoint
To apply a middleware on any endpoint, you can use the same router defined in `src/api/index.ts` or any other router that is used or exported by `src/api/index.ts`. For example:
:::warning
The examples used here don't apply Cross-Origin Resource Origin (CORS) options for simplicity. Make sure to apply them, especially for core routes, as explained in the [Create Endpoint](./create.mdx#cors-configuration) documentation.
:::
```ts title=src/api/index.ts
import { Router } from "express"
import {
customMiddleware,
} from "./middlewares/custom-middleware"
export default (rootDirectory, pluginOptions) => {
const router = Router()
// custom route
router.get("/hello", (req, res) => {
res.json({
message: "Welcome to My Store!",
})
})
// middleware for the custom route
router.use("/hello", customMiddleware)
// middleware for core route
router.use("/store/products", customMiddleware)
return router
}
```
## Step 3: Building Files
Similar to custom endpoints, you must transpile the files under `src` into the `dist` directory for the backend to load them.
To do that, run the following command before running the Medusa backend:
```bash npm2yarn
npm run build
```
You can then test that the middleware is working by running the backend.
---
## Registering New Resources in Dependency Container
In some cases, you may need to register a resource to use within your commerce application. For example, you may want to register the logged-in user to access it in other services. You can do that in your middleware.
:::tip
If you want to register a logged-in user and access it in your resources, you can check out [this example guide](./example-logged-in-user.mdx).
:::
To register a new resource in the dependency container, use the `req.scope.register` method:
```ts title=src/api/middlewares/custom-middleware.ts
export function customMiddleware(req, res, next) {
// TODO perform an action
req.scope.register({
customResource: {
resolve: () => "my custom resource",
},
})
next()
}
```
You can then load this new resource within other resources. For example, to load it in a service:
<!-- eslint-disable prefer-rest-params -->
```ts title=src/services/custom-service.ts
import { TransactionBaseService } from "@medusajs/medusa"
class CustomService extends TransactionBaseService {
constructor(container, options) {
super(...arguments)
// use the registered resource.
try {
container.customResource
} catch (e) {
// avoid errors when the backend first loads
}
}
}
export default CustomService
```
Notice that you have to wrap your usage of the new resource in a try-catch block when you use it in a constructor. This is to avoid errors that can arise when the backend first loads, as the resource is not registered yet.
### Note About Services Lifetime
If you want to access new registrations in the dependency container within a service, you must set the lifetime of the service either to `Lifetime.SCOPED` or `Lifetime.TRANSIENT`. Services that have a `Lifetime.SINGLETON` lifetime can't access new registrations since they're resolved and cached in the root dependency container beforehand. You can learn more in the [Create Services documentation](../services/create-service.mdx#service-life-time).
For custom services, no additional action is required as the default lifetime is `Lifetime.SCOPED`. However, if you extend a core service, you must change the lifetime since the default lifetime for core services is `Lifetime.SINGLETON`.
For example:
<!-- eslint-disable prefer-rest-params -->
```ts
import { Lifetime } from "awilix"
import {
ProductService as MedusaProductService,
} from "@medusajs/medusa"
// extending ProductService from the core
class ProductService extends MedusaProductService {
// The default life time for a core service is SINGLETON
static LIFE_TIME = Lifetime.SCOPED
constructor(container, options) {
super(...arguments)
// use the registered resource.
try {
container.customResource
} catch (e) {
// avoid errors when the backend first loads
}
}
// ...
}
export default ProductService
```
---
## Troubleshooting
<Troubleshooting
sections={[
{
title: 'AwilixResolutionError: Could Not Resolve X',
content: <ServiceLifetimeSection />
},
{
title: 'AwilixResolutionError: Could Not Resolve X (Custom Registration)',
content: <CustomRegistrationSection />
}
]}
/>
---
## See Also
- [Store API reference](https://docs.medusajs.com/api/store)
- [Admin API reference](https://docs.medusajs.com/api/admin)
@@ -0,0 +1,214 @@
---
addHowToData: true
---
import Troubleshooting from '@site/src/components/Troubleshooting'
import ServiceLifetimeSection from '../../troubleshooting/awilix-resolution-error/_service-lifetime.md'
import CustomRegistrationSection from '../../troubleshooting/awilix-resolution-error/_custom-registration.md'
# Middlewares
In this document, you’ll learn how to add a middleware to existing or custom API Routes in Medusa.
:::tip
v1.17.2 of `@medusajs/medusa` introduced a new approach to creating middlewares using a single `middlewares.ts` file. You can still use the [Express Router Approach](./add-middleware-express-route.mdx), however, it's highly recommended that you start using this new approach.
:::
## Basic Implementation
```ts title=src/api/middlewares.ts
import type { MiddlewaresConfig } from "@medusajs/medusa"
import type {
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
const storeMiddleware = (
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction
) => {
// do something
next()
}
export const config: MiddlewaresConfig = {
routes: [
{
matcher: "/store/*",
middlewares: [storeMiddleware],
},
],
}
```
A middleware is a function that has access to the `MedusaRequest` and `MedusaResponse` objects that are passed to API Route method handlers.
Middlewares are used to perform an action when a request is sent to an API Route, or modify the response of an API route, among other usages.
### middlewares.ts File
Middlewares are defined in the `src/api/middlewares.ts` file. This file must expose a config object of type `MiddlewaresConfig` imported from `@medusajs/medusa`.
This object accepts a parameter `routes`, whose value is an array of middleware route objects. Each middleware route object accepts the following properties:
- The `matcher` property accepts a string or a regular expression that will be used to check whether the middlewares should be applied on a API Routes.
- The `middlewares` property is an array of middlewares that should be applied on API Routes matching the pattern specified in `matcher`.
---
## Build Files
Similar to custom API Routes, you must transpile the files under `src` into the `dist` directory for the backend to load them.
To do that, run the following command before running the Medusa backend:
```bash npm2yarn
npm run build
```
You can then test that the middleware is working by running the backend.
---
## Register New Resources in Dependency Container
In some cases, you may need to register a resource to use within your commerce application. For example, you may want to register the logged-in user to access it in other resources, such as services. You can do that in your middleware.
:::tip
If you want to register a logged-in user and access it in your resources, you can check out [this example guide](./example-logged-in-user.mdx).
:::
To register a new resource in the dependency container, use the `MedusaRequest` object's `scope.register` method. It accepts an object, where each key is the name to be registered in the dependency container, and its value is an object that has a `resolve` property.
The `resolve`'s value is a function that returns the resource to be registered in the dependency container.
For example:
```ts title=src/api/middlewares.ts
import type { MiddlewaresConfig } from "@medusajs/medusa"
import type {
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
const customResource = (
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction) => {
req.scope.register({
customResource: {
resolve: () => "my custom resource",
},
})
next()
}
export const config: MiddlewaresConfig = {
routes: [
{
matcher: "/store/*",
middlewares: [customResource],
},
],
}
```
You can then load this new resource within other resources. For example, to load it in a service:
<!-- eslint-disable prefer-rest-params -->
```ts title=src/services/custom-service.ts
import { TransactionBaseService } from "@medusajs/medusa"
class CustomService extends TransactionBaseService {
constructor(container, options) {
super(...arguments)
// use the registered resource.
try {
container.customResource
} catch (e) {
// avoid errors when the backend first loads
}
}
}
export default CustomService
```
:::note
Make sure to wrap your usage of the new resource in a try-catch block when you use it in a constructor. This is to avoid errors that can arise when the backend first loads, as the resource isn't registered yet.
:::
### Note About Services Lifetime
If you want to access new registrations in the dependency container within a service, you must set the lifetime of the service either to `Lifetime.SCOPED` or `Lifetime.TRANSIENT`. Services that have a `Lifetime.SINGLETON` lifetime can't access new registrations since they're resolved and cached in the root dependency container beforehand. You can learn more in the [Create Services documentation](../services/create-service.mdx#service-life-time).
For custom services, no additional action is required as the default lifetime is `Lifetime.SCOPED`. However, if you extend a core service, you must change the lifetime since the default lifetime for core services is `Lifetime.SINGLETON`.
For example:
<!-- eslint-disable prefer-rest-params -->
```ts
import { Lifetime } from "awilix"
import {
ProductService as MedusaProductService,
} from "@medusajs/medusa"
// extending ProductService from the core
class ProductService extends MedusaProductService {
// The default life time for a core service is SINGLETON
static LIFE_TIME = Lifetime.SCOPED
constructor(container, options) {
super(...arguments)
// use the registered resource.
try {
container.customResource
} catch (e) {
// avoid errors when the backend first loads
}
}
// ...
}
export default ProductService
```
---
## Troubleshooting
<Troubleshooting
sections={[
{
title: 'AwilixResolutionError: Could Not Resolve X',
content: <ServiceLifetimeSection />
},
{
title: 'AwilixResolutionError: Could Not Resolve X (Custom Registration)',
content: <CustomRegistrationSection />
}
]}
/>
---
## See Also
- [Store API reference](https://docs.medusajs.com/api/store)
- [Admin API reference](https://docs.medusajs.com/api/admin)
@@ -0,0 +1,950 @@
---
addHowToData: true
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# Create Express Endpoint
In this document, you’ll learn how to create express endpoints in Medusa.
:::note
Following v1.17.2 of `@medusajs/medusa`, it's highly recommended to use the [API Routes](./create.mdx) instead. Future versions of Medusa may drop support of Express Endpoints.
:::
## Basic Implementation
To create a new endpoint, start by creating a new file in `src/api` called `index.ts`. At its basic format, `index.ts` should look something like this:
```ts title=src/api/index.ts
import { Router } from "express"
export default (rootDirectory, options) => {
const router = Router()
router.get("/hello", (req, res) => {
res.json({
message: "Welcome to My Store!",
})
})
return router
}
```
This exports a function that returns an Express router. The function receives two parameters:
- `rootDirectory` is the absolute path to the root directory that your backend is running from.
- `options` is an object that contains the configurations exported from `medusa-config.js`. If your API route is part of a plugin, then it will contain the plugin's options instead.
---
## Building Files
Custom endpoints 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:
```bash npm2yarn
npm run build
```
---
## Defining Multiple Routes or Middlewares
Instead of returning an Express router in the function, you can return an array of routes and [middlewares](./add-middleware-express-route.mdx).
For example:
```ts title=src/api/index.ts
import { Router } from "express"
export default (rootDirectory, options) => {
const router = Router()
router.get("/hello", (req, res) => {
res.json({
message: "Welcome to My Store!",
})
})
// you can also define the middleware
// in another file and import it
const middleware = (res, req, next) => {
// TODO define global middleware
console.log("hello from middleware")
next()
}
const anotherRouter = Router()
anotherRouter.get("/store/*", (req, res, next) => {
// TODO perform an actions for all store endpoints
next()
})
return [middleware, router, anotherRouter]
}
```
This allows you to export multiple routers and middlewares from the same file. You can also import the routers, routes, and middlewares from other files, then import them in `src/api/index.ts` instead of defining them within the same file.
---
## Endpoint Path
Your endpoint can be under any path you wish.
By Medusa’s conventions:
- All Storefront REST APIs are prefixed by `/store`. For example, the `/store/products` endpoint lets you retrieve the products to display them on your storefront.
- All Admin REST APIs are prefixed by `/admin`. For example, the `/admin/products` endpoint lets you retrieve the products to display them on your Admin.
You can also create endpoints that don't reside under these two prefixes, similar to the `hello` endpoint in the previous example.
---
## CORS Configuration
If you’re adding a storefront or admin endpoint and you want to access these endpoints from the storefront or Medusa admin, you need to pass your endpoints Cross-Origin Resource Origin (CORS) options using the `cors` package.
First, import the necessary utility functions and types from Medusa's packages with the `cors` package:
```ts
import {
getConfigFile,
parseCorsOrigins,
} from "medusa-core-utils"
import {
ConfigModule,
} from "@medusajs/medusa/dist/types/global"
import cors from "cors"
```
Next, in the exported function, retrieve the CORS configurations of your backend using the utility functions you imported:
```ts
export default (rootDirectory) => {
// ...
const { configModule } =
getConfigFile<ConfigModule>(rootDirectory, "medusa-config")
const { projectConfig } = configModule
// ....
}
```
Then, create an object that will hold the CORS configurations. Based on whether it's storefront or admin CORS options, you pass it the respective configuration from `projectConfig`:
<Tabs groupId="endpoint-type" isCodeTabs={true}>
<TabItem value="storefront" label="Storefront CORS" default>
```ts
const storeCorsOptions = {
origin: projectConfig.store_cors.split(","),
credentials: true,
}
```
</TabItem>
<TabItem value="admin" label="Admin CORS">
```ts
const adminCorsOptions = {
origin: projectConfig.admin_cors.split(","),
credentials: true,
}
```
</TabItem>
</Tabs>
Finally, you can either pass the `cors` middleware for a specific route, or pass it to the entire router:
<Tabs groupId="pass-type" isCodeTabs={true}>
<TabItem value="single" label="Pass to Endpoint" default>
```ts
adminRouter.options("/admin/hello", cors(adminCorsOptions))
adminRouter.get(
"/admin/hello",
cors(adminCorsOptions),
(req, res) => {
// ...
}
)
```
</TabItem>
<TabItem value="router" label="Pass to Router">
```ts
adminRouter.use(cors(adminCorsOptions))
```
</TabItem>
</Tabs>
---
## Parse Request Body Parameters
If you want to accept request body parameters, you need to pass express middlewares that parse the payload type to your router.
For example:
```ts title=src/api/index.ts
import bodyParser from "body-parser"
import express, { Router } from "express"
export default (rootDirectory, pluginOptions) => {
const router = Router()
router.use(express.json())
router.use(express.urlencoded({ extended: true }))
router.post("/store/hello", (req, res) => {
res.json({
message: req.body.name,
})
})
return router
}
```
In the code snippet above, you use the following middlewares:
- `express.json()`: parses requests with JSON payloads
- `express.urlencoded()`: parses requests with urlencoded payloads.
You can learn about other available middlewares in the [Express documentation](https://expressjs.com/en/api.html#express).
---
## Protected Routes
Protected routes are routes that should only be accessible by logged-in customers or users.
### Protect Store Routes
There are two approaches to make a storefront route protected:
- Using the `requireCustomerAuthentication` middleware, which disallows unauthenticated customers from accessing a route, and allows you to access the logged-in customer's ID.
- Using the `authenticateCustomer` middleware, which allows both authenticated and unauthenticated customers to access your route, but allows you to access the logged-in customer's ID as well.
To make a storefront route protected using either middlewares, first, import the middleware at the top of your file:
<!-- eslint-disable max-len -->
```ts
import { requireCustomerAuthentication } from "@medusajs/medusa"
// import { authenticateCustomer } from "@medusajs/medusa"
```
Then, pass the middleware to either a single route or an entire router:
<Tabs groupId="pass-type" isCodeTabs={true}>
<TabItem value="single" label="Pass to Endpoint" default>
```ts
// only necessary if you're passing cors options per route
router.options("/store/hello", cors(storeCorsOptions))
router.get(
"/store/hello",
cors(storeCorsOptions),
requireCustomerAuthentication(),
// authenticateCustomer()
async (req, res) => {
// access current customer
const id = req.user.customer_id
// if you're using authenticateCustomer middleware
// check if id is set first
const customerService = req.scope.resolve("customerService")
const customer = await customerService.retrieve(id)
// ...
}
)
```
</TabItem>
<TabItem value="router" label="Pass to Router">
```ts
storeRouter.use(requireCustomerAuthentication())
// all routes added to storeRouter are now protected
// the logged in customer can be accessed using:
// req.user.customer_id
// storeRouter.use(authenticateCustomer())
```
</TabItem>
</Tabs>
### Protect Admin Routes
To protect admin routes and only allow logged-in users from accessing them, first, import the `authenticate` middleware at the top of the file:
<!-- eslint-disable max-len -->
```ts
import { authenticate } from "@medusajs/medusa"
```
Then, pass the middleware to either a single route or an entire router:
<Tabs groupId="pass-type" isCodeTabs={true}>
<TabItem value="single" label="Pass to Endpoint" default>
```ts
// only necessary if you're passing cors options per route
adminRouter.options("/admin/hello", cors(adminCorsOptions))
adminRouter.get(
"/admin/hello",
cors(adminCorsOptions),
authenticate(),
async (req, res) => {
// access current user
const id = req.user.userId
const userService = req.scope.resolve("userService")
const user = await userService.retrieve(id)
// ...
}
)
```
</TabItem>
<TabItem value="router" label="Pass to Router">
```ts
adminRouter.use(authenticate())
// all routes added to adminRouter are now protected
// the logged in user can be accessed using:
// req.user.userId
```
</TabItem>
</Tabs>
---
## Retrieve Medusa Config
As mentioned, the second parameter `options` includes the configurations exported from `medusa-config.js`. However, in plugins it only includes the plugin's options.
If you need to access the Medusa configuration in your endpoint, you can use the `getConfigFile` method imported from `medusa-core-utils`. It accepts the following parameters:
1. `rootDirectory`: The first parameter is a string indicating root directory of your Medusa backend.
2. `config`: The second parameter is a string indicating the name of the config file, which should be `medusa-config` unless you change it.
The function returns an object with the following properties:
1. `configModule`: An object containing the configurations exported from `medusa-config.js`.
2. `configFilePath`: A string indicating absolute path to the configuration file.
3. `error`: if any errors occur, they'll be included as the value of this property. Otherwise, its value will be `undefined`.
Here's an example of retrieving the configurations within an endpoint using `getConfigFile`:
```ts title=src/api/index.ts
import { Router } from "express"
import { ConfigModule } from "@medusajs/medusa"
import { getConfigFile } from "medusa-core-utils"
export default (rootDirectory) => {
const router = Router()
const { configModule } = getConfigFile<ConfigModule>(
rootDirectory,
"medusa-config"
)
router.get("/store-cors", (req, res) => {
res.json({
store_cors: configModule.projectConfig.store_cors,
})
})
return router
}
```
Notice that `getConfigFile` is a generic function. So, if you're using TypeScript, you should pass it the type `ConfigModule` imported from `@medusajs/medusa`.
If you're accessing custom configurations, you'll need to create a new type that defines these configurations. For example:
```ts title=src/api/index.ts
import { Router } from "express"
import { ConfigModule } from "@medusajs/medusa"
import { getConfigFile } from "medusa-core-utils"
type MyConfigModule = ConfigModule & {
projectConfig: {
custom_config?: string
}
}
export default (rootDirectory) => {
const router = Router()
const { configModule } = getConfigFile<MyConfigModule>(
rootDirectory,
"medusa-config"
)
router.get("/hello", (req, res) => {
res.json({
custom_config: configModule.projectConfig.custom_config,
})
})
return router
}
```
---
## Handle Errors
As Medusa uses v4 of Express, you need to manually handle errors thrown asynchronously as explained in [Express's documentation](https://expressjs.com/en/guide/error-handling.html).
You can use [middlewares](./add-middleware-express-route.mdx) to handle errors. You can also use middlewares defined by Medusa, which ensure that your error handling is consistent across your Medusa backend.
:::note
Code snippets are taken from the [full example available at the end of this document](#example-crud-endpoints).
:::
To handle errors using Medusa's middlewares, first, import the `errorHandler` middleware from `@medusajs/medusa` and apply it on your routers. Make sure it's applied after all other middlewares and routes:
```ts title=src/api/index.ts
import express, { Router } from "express"
import adminRoutes from "./admin"
import storeRoutes from "./store"
import { errorHandler } from "@medusajs/medusa"
export default (rootDirectory, options) => {
const router = Router()
router.use(express.json())
router.use(express.urlencoded({ extended: true }))
adminRoutes(router, options)
storeRoutes(router, options)
router.use(errorHandler())
return router
}
```
Then, wrap the function handler of every route with the `wrapHandler` middleware imported from `@medusajs/medusa`. For example:
```ts title=src/api/admin.ts
import { wrapHandler } from "@medusajs/medusa"
// ...
export default function adminRoutes(
router: Router,
options: ConfigModule
) {
// ...
adminRouter.get("/posts", wrapHandler(async (req, res) => {
const postService: PostService =
req.scope.resolve("postService")
res.json({
posts: await postService.list(),
})
}))
// ...
}
```
Alternatively, you can define the endpoints in different files, and import and use them in your router:
<!-- eslint-disable @typescript-eslint/no-var-requires -->
```ts title=src/api/admin.ts
import { wrapHandler } from "@medusajs/medusa"
// ...
export default function adminRoutes(
router: Router,
options: ConfigModule
) {
// ...
adminRouter.get(
"/posts",
wrapHandler(require("./list-posts").default)
)
// ...
}
```
Now all errors thrown in your custom endpoints, including in their custom services, will be caught and returned to the user.
However, if you throw errors like this:
```ts
throw new Error ("Post was not found")
```
You'll notice that the endpoint returns the following object error in the response:
```json
{
"code": "unknown_error",
"type": "unknown_error",
"message": "An unknown error occurred."
}
```
To ensure the error message is relayed in the response, it's recommended to use `MedusaError` imported from `@medusajs/utils` as the thrown error instead:
```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 type of the error. 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 can be returned in the error object.
After using `MedusaError`, you'll notice that the returned error in the response provides a clearer message:
```json
{
"type": "not_found",
"message": "Post was not found"
}
```
---
## Use Other Resources
### Entities and Repositories
Your endpoints likely perform an action on an entity. For example, you may create an endpoint to retrieve a list of posts.
You can perform actions on an entity either through its [Repository](../entities/overview.mdx#what-are-repositories) or through a [service](#services). This section covers how to retrieve a repository in an endpoint, but it's recommended to use services instead.
You can retrieve any registered resource, including repositories, in your endpoint using `req.scope.resolve` passing it the resource's registration name in the [dependency container](../fundamentals/dependency-injection.md). Repositories are registered as their camel-case name. So, for example, if you have a `PostRepository`, it's registered as `postRepository`.
Here’s an example of an endpoint that retrieves the list of posts in a store:
:::note
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
import { PostRepository } from "../repositories/post"
import { EntityManager } from "typeorm"
// ...
export default () => {
// ...
storeRouter.get("/posts", async (req, res) => {
const postRepository: typeof PostRepository =
req.scope.resolve("postRepository")
const manager: EntityManager = req.scope.resolve("manager")
const postRepo = manager.withRepository(postRepository)
return res.json({
posts: await postRepo.find(),
})
})
// ...
}
```
Notice that to retrieve an instance of the repository, you need to retrieve first Typeorm's Entity manager and use its `withRepository` method.
### Services
Services in Medusa bundle a set of functionalities into one class. Typically, these functionalities are associated with an entity, such as methods to retrieve, create, or update its records.
You can retrieve any registered resource, including services, in your endpoint using `req.scope.resolve` passing it the service’s registration name in the [dependency container](../fundamentals/dependency-injection.md). Services are registered as their camel-case name. So, for example, if you have a `PostService`, it's registered as `postService`.
Here’s an example of an endpoint that retrieves the list of posts in a store:
:::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
storeRouter.get("/posts", async (req, res) => {
const postService: PostService = req.scope.resolve(
"postService"
)
return res.json({
posts: await postService.list(),
})
})
```
### Other Resources
Any resource that is registered in the dependency container, such as strategies or file services, can be accessed through `req.scope.resolve`. Refer to the [dependency injection](../fundamentals/dependency-injection.md) documentation for details on registered resources.
---
## Example: CRUD Endpoints
This section services as an example of creating endpoints that perform Create, Read, Update, and Delete (CRUD) operations. Note that all admin endpoints are placed in `src/api/admin.ts`, and store endpoints are placed in `src/api/store.ts`. You can also place each endpoint in a separate file, import it, and add it to its respective router.
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="index" label="src/api/index.ts" default>
```ts
import express, { Router } from "express"
import adminRoutes from "./admin"
import storeRoutes from "./store"
import { errorHandler } from "@medusajs/medusa"
export default (rootDirectory, options) => {
const router = Router()
router.use(express.json())
router.use(express.urlencoded({ extended: true }))
adminRoutes(router, options)
storeRoutes(router, options)
router.use(errorHandler())
return router
}
```
</TabItem>
<TabItem value="admin" label="src/api/admin.ts">
```ts
import { Router } from "express"
import PostService from "../services/post"
import {
ConfigModule,
} from "@medusajs/medusa/dist/types/global"
import cors from "cors"
import { authenticate, wrapHandler } from "@medusajs/medusa"
import AuthorService from "../services/author"
export default function adminRoutes(
router: Router,
options: ConfigModule
) {
const { projectConfig } = options
const corsOptions = {
origin: projectConfig.admin_cors.split(","),
credentials: true,
}
const adminRouter = Router()
router.use("/admin/blog", adminRouter)
adminRouter.use(cors(corsOptions))
adminRouter.use(authenticate())
// it's recommended to define the routes
// in separate files. They're done in
// the same file here for simplicity
// list all blog posts
adminRouter.get(
"/posts",
wrapHandler(async (req, res) => {
const postService: PostService = req.scope.resolve(
"postService"
)
res.json({
posts: await postService.list(),
})
}))
// retrieve a single blog post
adminRouter.get(
"/posts/:id",
wrapHandler(async (req, res) => {
const postService: PostService = req.scope.resolve(
"postService"
)
const post = await postService.retrieve(req.params.id)
res.json({
post,
})
}))
// create a blog post
adminRouter.post(
"/posts",
wrapHandler(async (req, res) => {
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,
})
}))
// update a blog post
adminRouter.post(
"/posts/:id",
wrapHandler(async (req, res) => {
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 blog post
adminRouter.delete(
"/posts/:id",
wrapHandler(async (req, res) => {
const postService: PostService = req.scope.resolve(
"postService"
)
await postService.delete(req.params.id)
res.status(200).end()
}))
// list all blog authors
adminRouter.get(
"/authors",
wrapHandler(async (req, res) => {
const authorService: AuthorService = req.scope.resolve(
"authorService"
)
res.json({
authors: await authorService.list(),
})
}))
// retrieve a single blog author
adminRouter.get(
"/authors/:id",
wrapHandler(async (req, res) => {
const authorService: AuthorService = req.scope.resolve(
"authorService"
)
res.json({
post: await authorService.retrieve(req.params.id),
})
}))
// create a blog author
adminRouter.post(
"/authors",
wrapHandler(async (req, res) => {
const authorService: AuthorService = req.scope.resolve(
"authorService"
)
// basic validation of request body
if (!req.body.name) {
throw new Error("`name` is required.")
}
const author = await authorService.create(req.body)
res.json({
author,
})
}))
// update a blog author
adminRouter.post(
"/authors/:id",
wrapHandler(async (req, res) => {
const authorService: AuthorService = req.scope.resolve(
"authorService"
)
// basic validation of request body
if (req.body.id) {
throw new Error("Can't update author ID")
}
const author = await authorService.update(
req.params.id,
req.body
)
res.json({
author,
})
}))
// delete a blog author
adminRouter.delete(
"/authors/:id",
wrapHandler(async (req, res) => {
const authorService: AuthorService = req.scope.resolve(
"authorService"
)
await authorService.delete(req.params.id)
res.status(200).end()
}))
}
```
</TabItem>
<TabItem value="store" label="src/api/store.ts">
```ts
import { Router } from "express"
import {
ConfigModule,
} from "@medusajs/medusa/dist/types/global"
import PostService from "../services/post"
import cors from "cors"
import AuthorService from "../services/author"
import { wrapHandler } from "@medusajs/medusa"
export default function storeRoutes(
router: Router,
options: ConfigModule
) {
const { projectConfig } = options
const storeCorsOptions = {
origin: projectConfig.store_cors.split(","),
credentials: true,
}
const storeRouter = Router()
router.use("/store/blog", storeRouter)
storeRouter.use(cors(storeCorsOptions))
// list all blog posts
storeRouter.get(
"/posts",
wrapHandler(async (req, res) => {
const postService: PostService = req.scope.resolve(
"postService"
)
res.json({
posts: await postService.list(),
})
}))
// retrieve a single blog post
storeRouter.get(
"/posts/:id",
wrapHandler(async (req, res) => {
const postService: PostService = req.scope.resolve(
"postService"
)
res.json({
post: await postService.retrieve(req.params.id),
})
}))
// list all blog authors
storeRouter.get(
"/authors",
wrapHandler(async (req, res) => {
const authorService: AuthorService = req.scope.resolve(
"authorService"
)
res.json({
authors: await authorService.list(),
})
}))
// retrieve a single blog author
storeRouter.get(
"/authors/:id",
wrapHandler(async (req, res) => {
const authorService: AuthorService = req.scope.resolve(
"authorService"
)
res.json({
post: await authorService.retrieve(req.params.id),
})
}))
}
```
</TabItem>
</Tabs>
---
## See Also
- [Storefront API Reference](https://docs.medusajs.com/api/store)
- [Admin API Reference](https://docs.medusajs.com/api/admin)
@@ -0,0 +1,744 @@
---
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
CORS configurations are automatically added to custom API Routes defined under the `/store` or `/admin` path prefixes based on the [store_cors and admin_cors configurations](../backend/configurations.md#admin_cors-and-store_cors) respectively.
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,
}),
],
},
],
}
```
---
## 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.
---
## 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 provides an `errorHandler` middleware that you can use on your custom API Routes so that your error handling is consistent with the Medusa backend. You can also create custom [middlewares](./add-middleware.mdx) to handle errors.
To handle errors using Medusa's middlewares, first, import the `errorHandler` middleware from `@medusajs/medusa` and apply it on your routes.
For example:
```ts title=src/api/middlewares.ts
import {
errorHandler,
type MiddlewaresConfig,
} from "@medusajs/medusa"
export const config: MiddlewaresConfig = {
routes: [
{
matcher: "/store/custom*",
middlewares: [errorHandler()],
},
],
}
```
Make sure it's applied after all other middlewares.
Then, wrap the method handler function of every API route method with the `wrapHandler` function imported from `@medusajs/medusa`. For example:
```ts title=src/api/store/custom/route.ts
import {
MedusaRequest,
MedusaResponse,
wrapHandler,
} from "@medusajs/medusa"
export const GET = wrapHandler(async (
req: MedusaRequest,
res: MedusaResponse
) => {
res.json({
message: "[GET] Hello world!",
})
})
export const POST = wrapHandler(async (
req: MedusaRequest,
res: MedusaResponse
) => {
res.json({
message: "[POST] Hello world!",
})
})
```
Now all errors thrown in your custom API Routes, or in resources you use within your API Route such as services, are caught and returned to the user.
### Using MedusaError
If you throw errors 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, it's recommended to use `MedusaError` imported from `@medusajs/utils` as the thrown error 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"
}
```
---
## 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)
@@ -0,0 +1,171 @@
---
addHowToData: true
---
import Troubleshooting from '@site/src/components/Troubleshooting'
import ServiceLifetimeSection from '../../troubleshooting/awilix-resolution-error/_service-lifetime.md'
import CustomRegistrationSection from '../../troubleshooting/awilix-resolution-error/_custom-registration.md'
# Example: Access Logged-In User
This document gives an example of how you can use middlewares to register the logged-in user in the dependency container of your commerce application. You can then access the logged-in user in other resources, such as services.
:::tip
You can apply the same steps if you want to register the current customer.
:::
Learn more about [middlewares in its guide](./add-middleware.mdx).
## Step 1: Create the Middleware
Create the file `src/api/middlewares.ts` with the following content:
```ts title=src/api/middlewares.ts
import type {
MiddlewaresConfig,
User,
UserService,
} from "@medusajs/medusa"
import type {
MedusaNextFunction,
MedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
const registerLoggedInUser = async (
req: MedusaRequest,
res: MedusaResponse,
next: MedusaNextFunction
) => {
let loggedInUser: User | null = null
if (req.user && req.user.userId) {
const userService =
req.scope.resolve("userService") as UserService
loggedInUser = await userService.retrieve(req.user.userId)
}
req.scope.register({
loggedInUser: {
resolve: () => loggedInUser,
},
})
next()
}
export const config: MiddlewaresConfig = {
routes: [
{
matcher: "/admin/products",
middlewares: [registerLoggedInUser],
},
],
}
```
This creates a `registerLoggedInUser` middleware that handles registering the logged-in user in the dependency container.
It then applies this middleware on the `/admin/products` API Route. You can change that to be the route of any other API Route or a pattern that matches multiple routes.
---
## Step 2: Use in a Service
Access the logged-in user resource in the constructor of a service:
<!-- eslint-disable prefer-rest-params -->
```ts
import { Lifetime } from "awilix"
import {
TransactionBaseService,
User,
} from "@medusajs/medusa"
class HelloService extends TransactionBaseService {
protected readonly loggedInUser_: User | null
constructor(container, options) {
super(...arguments)
try {
this.loggedInUser_ = container.loggedInUser
} catch (e) {
// avoid errors when backend first runs
}
}
// ...
}
export default HelloService
```
If you're accessing it in an extended core service, it’s important to change the lifetime of the service to `Lifetime.SCOPED`. For example:
<!-- eslint-disable prefer-rest-params -->
```ts
import { Lifetime } from "awilix"
import {
ProductService as MedusaProductService,
User,
} from "@medusajs/medusa"
// extend core product service
class ProductService extends MedusaProductService {
// The default life time for a core service is SINGLETON
static LIFE_TIME = Lifetime.SCOPED
protected readonly loggedInUser_: User | null
constructor(container, options) {
super(...arguments)
this.loggedInUser_ = container.loggedInUser
}
}
export default ProductService
```
You can learn more about the importance of changing the service lifetime in the [Middlewares documentation](./add-middleware.mdx#note-about-services-lifetime).
---
## Step 3: Test it Out
To test out your implementation, run the following command in the root directory of the Medusa backend to transpile your changes:
```bash npm2yarn
npm run build
```
Then, run your backend with the following command:
```bash npm2yarn
npx medusa develop
```
If you try accessing the API Routes you added the middleware to, you should see your implementation working as expected.
---
## Troubleshooting
<Troubleshooting
sections={[
{
title: 'AwilixResolutionError: Could Not Resolve X',
content: <ServiceLifetimeSection />
},
{
title: 'AwilixResolutionError: Could Not Resolve X (Custom Registration)',
content: <CustomRegistrationSection />
}
]}
/>
@@ -0,0 +1,82 @@
---
addHowToData: true
---
# How to Extend an API Route Validator
In this guide, you'll learn how to extend an API Route validator from the Medusa core.
## Overview
Request fields passed to API Routes defined in the Medusa core are validated to ensure that only expected fields are passed, and the passed fields are of correct types.
In some scenarios, you may need to allow passing custom fields into an existing API Route. If a custom field is passed to an API Route in the core, the API Route returns an error in the response.
To allow passing custom fields into core API Routes, you must extend Validators. Validators are classes that are used by the core to validate the request parameters to an API Route.
This guide explains how to extend a validator to allow passing custom fields to an API Route. You'll be extending the validator of the admin API Create Product route as an example.
---
## Prerequisites
This guide assumes you already have a Medusa backend installed and configured. If not, you can check out the [backend quickstart guide](../backend/install.mdx).
---
## Step 1: Create File
You can add the code to extend the validator in any file under the `src` directory of your Medusa project, but it should be executed by `src/api/index.ts`.
For example, you can add the code in an exported function defined in the file `src/api/routes/admin/products/create-product.ts`, then import that file in `src/api/index.ts` and execute the function.
For simplicity, this guide adds the code directly in `src/api/index.ts`. Make sure to create it if it's not already created.
---
## Step 2: Extend Validator
In the file you created, which in this case is `src/api/index.ts`, add the following content to extend the validator:
<!-- eslint-disable max-len -->
```ts title=src/api/index.ts
import { registerOverriddenValidators } from "@medusajs/medusa"
import {
AdminPostProductsReq as MedusaAdminPostProductsReq,
} from "@medusajs/medusa/dist/api/routes/admin/products/create-product"
import { IsString } from "class-validator"
class AdminPostProductsReq extends MedusaAdminPostProductsReq {
@IsString()
custom_field: string
}
registerOverriddenValidators(AdminPostProductsReq)
```
In this code snippet you:
1. Import the `registerOverriddenValidators` function from the `@medusajs/medusa` package. This utility function allows you to extend validators in the core.
2. Import the `AdminPostProductsReq` class from `@medusajs/medusa` as `MedusaAdminPostProductsReq` since this guide extends the Create Product API Route validator. If you're extending a different validator, make sure to import it instead.
3. Create a class `AdminPostProductsReq` that extends `MedusaAdminPostProductsReq` and adds a new field `custom_field`. Notice that the name of the class must be the same name of the validator defined in the core. `custom_field` has the type `string`. You can change the type or name of the field, or add more fields.
4. Call `registerOverriddenValidators` passing it the `AdminPostProductsReq` class you created. This will override the validator defined in the core to include the new field `custom_field` among the existing fields defined in the core.
:::tip
Validators are defined in the same file as the API Route. To find the validator you need to override, find the API Route file under `@medusajs/medusa/dist/api/routes` and import the validator in that file.
:::
---
## Step 3: Test it Out
To test out your extended validator, build and start your Medusa backend:
```bash npm2yarn
npm run build
npx medusa develop
```
Then, send a request to the API Route you extended passing it your custom fields. To test out the example in this guide, send an [authenticated request](https://docs.medusajs.com/api/admin#authentication) to the [Create Product API Route](https://docs.medusajs.com/api/admin#products_postproducts) and pass it the `custom_field` body parameter. The request should execute with no errors.
@@ -0,0 +1,68 @@
import DocCardList from '@theme/DocCardList';
import Icons from '@theme/Icon';
# API Routes
In this document, you’ll learn what API Routes are in Medusa.
## Introduction
The Medusa Backend is a Node.js headless server. It exposes commerce functionalities implemented by Medusa's [commerce module](../../modules/overview.mdx) through REST APIs that external or frontend applications can access to process or retrieve data.
The backend exposes two types of API routes: Store APIs and Admin APIs. The Store APIs are typically accessed from the storefront. For example, you can use the Store APIs to show customers available products or implement a cart and checkout flow.
The Admin APIs are typically accessed from an admin dashboard. For example, you can use the Admin APIs to allow admins to manage the store’s data such as products, orders, and so on.
<DocCardList colSize={6} items={[
{
type: 'link',
href: 'https://docs.medusajs.com/api/store',
label: 'Store APIs',
customProps: {
icon: Icons['server-solid'],
description: 'Check out available Store REST APIs.'
}
},
{
type: 'link',
href: 'https://docs.medusajs.com/api/admin',
label: 'Admin APIs',
customProps: {
icon: Icons['server-solid'],
description: 'Check out available Admin REST APIs.'
}
},
]} />
---
## Custom Development
Aside from using the Medusa backend's API routes, developers can create their own API routes either directly in the Medusa Backend or in a plugin.
:::tip
As the core Medusa package is completely customizable, developers can also extend the functionality even further to implement GraphQL routes.
:::
<DocCardList colSize={6} items={[
{
type: 'link',
href: '/development/api-routes/create',
label: 'Create an API Route',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to create an API Route in Medusa.'
}
},
{
type: 'link',
href: '/development/api-routes/add-middleware',
label: 'Add a Middleware',
customProps: {
icon: Icons['academic-cap-solid'],
description: 'Learn how to add a middleware in Medusa.'
}
},
]} />