docs: add documentation for v1.8 (#3669)

This commit is contained in:
Shahed Nasser
2023-04-03 13:50:59 +02:00
committed by GitHub
parent 0cca13779d
commit c6bfad14d8
123 changed files with 7610 additions and 2697 deletions
@@ -3,7 +3,7 @@ description: 'Learn how to add a middleware in Medusa. A middleware is a functio
addHowToData: true
---
# How to Add a Middleware
# Middlewares
In this document, youll learn how to add a middleware to an existing or custom route in Medusa.
@@ -19,36 +19,17 @@ You can add a middleware to an existing route in the Medusa backend, a route in
---
## Add a Middleware
## How to Add a Middleware
Adding a middleware is very similar to adding a custom endpoint. The middleware must be created either in the `src/api/index.ts` entry point, or other TypeScript or JavaScript files imported into `src/api/index.ts`.
### Step 1: Create the Middleware File
:::info
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.
Learn more about creating custom endpoints in [this documentation](./create.md).
Each file should export a middleware function that accepts three parameters:
:::
The following code snippet is an example of adding a middleware:
```ts title=src/api/index.ts
import { Router } from "express"
export default () => {
const router = Router()
router.use("/store/products", (req, res, next) => {
// perform an action when retrieving products
next()
})
return router
}
```
This code snippet adds a middleware to the [List Products](/api/store/#tag/Product/operation/GetProducts) endpoint. In the middleware function, you can perform any action.
Then, you must call the `next` method received as a third parameter in the middleware function to ensure that the endpoint executes after the middleware.
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
@@ -56,9 +37,53 @@ You can learn more about Middlewares and their capabilities in [Expresss docu
:::
---
Here's an example of a middleware:
## Building Files
```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.md#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.
@@ -68,6 +93,100 @@ To do that, run the following command before running the Medusa backend:
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.md).
:::
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.md#service-life-time).
For custom services, no additional action is required as the default lifetime is `Lifetime.TRANSIENT`. 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
```
---
## See Also
@@ -0,0 +1,193 @@
---
description: 'In this document, youll 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
---
# Example: Access Logged-In User
In this document, youll 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.md) and [endpoints](./create.md). You can refer to their respective guides for more details about each.
## Step 1: Create the Middleware
Create the file `src/api/middlewareds/logged-in-user.ts` with the following content:
```ts title=src/api/middlewareds/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
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.md#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 async function (rootDirectory: string) {
const config = await 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, its 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.md#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
npm run start
```
If you try accessing the endpoints you added the middleware to, you should see your implementation working as expected.