951 lines
27 KiB
Plaintext
951 lines
27 KiB
Plaintext
---
|
||
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)
|