docs: create docs workspace (#5174)
* docs: migrate ui docs to docs universe * created yarn workspace * added eslint and tsconfig configurations * fix eslint configurations * fixed eslint configurations * shared tailwind configurations * added shared ui package * added more shared components * migrating more components * made details components shared * move InlineCode component * moved InputText * moved Loading component * Moved Modal component * moved Select components * Moved Tooltip component * moved Search components * moved ColorMode provider * Moved Notification components and providers * used icons package * use UI colors in api-reference * moved Navbar component * used Navbar and Search in UI docs * added Feedback to UI docs * general enhancements * fix color mode * added copy colors file from ui-preset * added features and enhancements to UI docs * move Sidebar component and provider * general fixes and preparations for deployment * update docusaurus version * adjusted versions * fix output directory * remove rootDirectory property * fix yarn.lock * moved code component * added vale for all docs MD and MDX * fix tests * fix vale error * fix deployment errors * change ignore commands * add output directory * fix docs test * general fixes * content fixes * fix announcement script * added changeset * fix vale checks * added nofilter option * fix vale error
This commit is contained in:
216
www/apps/docs/content/development/endpoints/add-middleware.mdx
Normal file
216
www/apps/docs/content/development/endpoints/add-middleware.mdx
Normal file
@@ -0,0 +1,216 @@
|
||||
---
|
||||
description: 'Learn how to add a middleware in Medusa. A middleware is a function that has access to the request and response objects and can be used to perform actions around an endpoint.'
|
||||
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 an existing or custom route in Medusa.
|
||||
|
||||
## 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)
|
||||
953
www/apps/docs/content/development/endpoints/create.mdx
Normal file
953
www/apps/docs/content/development/endpoints/create.mdx
Normal file
@@ -0,0 +1,953 @@
|
||||
---
|
||||
description: 'Learn how to create endpoints in Medusa. This guide also includes how to add CORS configurations, creating multiple endpoints, adding protected routes, and more.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Create Endpoints
|
||||
|
||||
In this document, you’ll learn how to create endpoints in Medusa.
|
||||
|
||||
## Overview
|
||||
|
||||
Custom endpoints are created under the `src/api` directory in your Medusa Backend. They're defined in a TypeScript or JavaScript file named `index` (for example, `index.ts`). This file should export a function that returns an Express router or an array of routes and middlewares.
|
||||
|
||||
To consume the custom endpoints in your Medusa backend, you must transpile them with the `build` command before starting your backend.
|
||||
|
||||
---
|
||||
|
||||
## 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.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.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,220 @@
|
||||
---
|
||||
description: 'In this document, you’ll see an example of how you can use middlewares and endpoints to register the logged-in user in the dependency container of your commerce application.'
|
||||
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
|
||||
|
||||
In this document, you’ll see an example of how you can use middlewares and endpoints 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.
|
||||
|
||||
This guide showcases how to register the logged-in admin user, but you can apply the same steps if you want to register the current customer.
|
||||
|
||||
This documentation does not explain the basics of [middlewares](./add-middleware.mdx) and [endpoints](./create.mdx). You can refer to their respective guides for more details about each.
|
||||
|
||||
## Step 1: Create the Middleware
|
||||
|
||||
Create the file `src/api/middlewares/logged-in-user.ts` with the following content:
|
||||
|
||||
```ts title=src/api/middlewares/logged-in-user.ts
|
||||
import { User, UserService } from "@medusajs/medusa"
|
||||
|
||||
export async function registerLoggedInUser(req, res, next) {
|
||||
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()
|
||||
}
|
||||
```
|
||||
|
||||
This retrieves the ID of the current user to retrieve an instance of it, then registers it in the scope under the name `loggedInUser`.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Apply Middleware on Endpoint
|
||||
|
||||
If you don't have the `cors` package installed, make sure to install it first:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install cors
|
||||
```
|
||||
|
||||
Then, create the file `src/api/routes/create-product.ts` with the following content:
|
||||
|
||||
```ts title=src/api/routes/create-product.ts
|
||||
import cors from "cors"
|
||||
import { Router } from "express"
|
||||
import {
|
||||
registerLoggedInUser,
|
||||
} from "../middlewares/logged-in-user"
|
||||
import
|
||||
authenticate
|
||||
from "@medusajs/medusa/dist/api/middlewares/authenticate"
|
||||
|
||||
const router = Router()
|
||||
|
||||
export default function (adminCorsOptions) {
|
||||
// This router will be applied before the core routes.
|
||||
// Therefore, the middleware will be executed
|
||||
// before the create product handler is hit
|
||||
router.use(
|
||||
"/admin/products",
|
||||
cors(adminCorsOptions),
|
||||
authenticate(),
|
||||
registerLoggedInUser
|
||||
)
|
||||
return router
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, the middleware is applied on the `/admin/products` core endpoint. However, you can apply it on any other endpoint. You can also apply it to custom endpoints.
|
||||
|
||||
For endpoints that require Cross-Origin Resource Origin (CORS) options, such as core endpoints, you must pass the CORS options to the middleware as well since it will be executed before the underlying endpoint.
|
||||
|
||||
:::tip
|
||||
|
||||
In the above code snippet, the `authenticate` middleware imported from `@medusajs/medusa` is used to ensure that the user is logged in first. If you're implementing this for middleware to register the logged-in customer, make sure to use the [customer's authenticate middleware](./create.mdx#protect-store-routes).
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Register Endpoint in the API
|
||||
|
||||
Create the file `src/api/index.ts` with the following content:
|
||||
|
||||
```ts title=src/api/index.ts
|
||||
import configLoader from "@medusajs/medusa/dist/loaders/config"
|
||||
import createProductRouter from "./routes/create-product"
|
||||
|
||||
export default function (rootDirectory: string) {
|
||||
const config = configLoader(rootDirectory)
|
||||
|
||||
const adminCors = {
|
||||
origin: config.projectConfig.admin_cors.split(","),
|
||||
credentials: true,
|
||||
}
|
||||
|
||||
const productRouters = [
|
||||
createProductRouter(adminCors),
|
||||
]
|
||||
|
||||
return [...productRouters]
|
||||
}
|
||||
```
|
||||
|
||||
This exports an array of endpoints, one of them being the product endpoint that you applied the middleware on in the second step. You can export more endpoints as well.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Use in a Service
|
||||
|
||||
You can now access the logged-in user in a service. For example, to access it in a custom 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 5: 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 endpoints 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,83 @@
|
||||
---
|
||||
description: 'Learn how to extend a validator. This is useful when you want to pass additional data to endpoints in the Medusa core.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
# How to Extend an Endpoint Validator
|
||||
|
||||
In this guide, you'll learn how to extend an endpoint validator from the Medusa core.
|
||||
|
||||
## Overview
|
||||
|
||||
Request fields passed to endpoints that are 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 endpoint. If a custom field is passed to an endpoint in the core, the endpoint returns an error in the response.
|
||||
|
||||
To allow passing custom fields into core endpoints, you must extend Validators. Validators are classes that are used by the core to validate the request parameters to an endpoint.
|
||||
|
||||
This guide explains how to extend a validator to allow passing custom fields to an endpoint. You'll be extending the validator of the admin API Create Product endpoint 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 endpoint 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 endpoint. To find the validator you need to override, find the endpoint 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 endpoint 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 endpoint](https://docs.medusajs.com/api/admin#products_postproducts) and pass it the `custom_field` body parameter. The request should execute with no errors.
|
||||
76
www/apps/docs/content/development/endpoints/overview.mdx
Normal file
76
www/apps/docs/content/development/endpoints/overview.mdx
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
description: "Learn what endpoints are in Medusa. Endpoints are REST APIs that allow a frontend or external system to interact with the Backend."
|
||||
---
|
||||
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import Icons from '@theme/Icon';
|
||||
|
||||
# Endpoints
|
||||
|
||||
In this document, you’ll learn what endpoints are in Medusa.
|
||||
|
||||
## Introduction
|
||||
|
||||
The Medusa Backend is a web server built on top of [Express](https://expressjs.com/), a Node.js web framework. This provides developers with all the functionalities available within Express during development. One of those are endpoints.
|
||||
|
||||
Endpoints are REST APIs that allow a frontend or an external system to interact with the Medusa Backend to retrieve and process data, or perform business logic. Endpoints are [Express routes](https://expressjs.com/en/starter/basic-routing.html).
|
||||
|
||||
Each [commerce module](../../modules/overview.mdx) contains a set of endpoints specific to the functionalities that it provides. Since the core package that powers the Medusa Backend acts as an orchestrator of commerce modules and exposes their endpoints, the endpoints of each of these commerce modules are available within the Medusa Backend.
|
||||
|
||||
The commerce modules provide two types of endpoints: 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 endpoints that commerce modules, developers can create their own REST APIs either directly in the Medusa Backend, in a plugin, or in a custom commerce module.
|
||||
|
||||
:::tip
|
||||
|
||||
As the core Medusa package is completely customizable, developers can also extend the functionality even further to implement GraphQL endpoints.
|
||||
|
||||
:::
|
||||
|
||||
<DocCardList colSize={6} items={[
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/endpoints/create',
|
||||
label: 'Create an Endpoint',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to create an endpoint in Medusa.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/development/endpoints/add-middleware',
|
||||
label: 'Add a Middleware',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to add a middleware in Medusa.'
|
||||
}
|
||||
},
|
||||
]} />
|
||||
Reference in New Issue
Block a user