docs: revise brand API route (#10352)

* docs: revise brand API route

* address feedback

* add directory images

* change sidebar title

* address comments
This commit is contained in:
Shahed Nasser
2024-12-04 12:19:42 +02:00
committed by GitHub
parent a5c8cc992c
commit 563b725ed6
6 changed files with 189 additions and 53 deletions
@@ -1,16 +1,16 @@
import { Prerequisites } from "docs-ui" import { Prerequisites } from "docs-ui"
export const metadata = { export const metadata = {
title: `${pageNumber} Create Brand API Route`, title: `${pageNumber} Guide: Create Brand API Route`,
} }
# {metadata.title} # {metadata.title}
<Note title="Example Chapter"> In the previous two chapters, you created a [Brand Module](../module/page.mdx) that added the concepts of brands to your application, then created a [workflow to create a brand](../workflow/page.mdx). In this chapter, you'll expose an API route that allows admin users to create a brand using the workflow from the previous chapter.
This chapter covers how to define an API route that creates a brand as the last step of the ["Build Custom Features" chapter](../page.mdx). An API Route is an endpoint that acts as an entry point for other clients to interact with your Medusa customizations, such as the admin dashboard, storefronts, or third-party systems.
</Note> The Medusa core application provides a set of [admin](!api!/admin) and [store](!api!/store) API routes out-of-the-box. You can also create custom API routes to expose your custom functionalities.
<Prerequisites <Prerequisites
items={[ items={[
@@ -21,34 +21,9 @@ This chapter covers how to define an API route that creates a brand as the last
]} ]}
/> />
Create the file `src/api/admin/brands/route.ts` with the following content: ## 1. Create the API Route
```ts title="src/api/admin/brands/route.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports" You create an API route in a `route.{ts,js}` file under a sub-directory of the `src/api` directory. The file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).
import {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import {
CreateBrandInput,
createBrandWorkflow,
} from "../../../workflows/create-brand"
export const POST = async (
req: MedusaRequest<CreateBrandInput>,
res: MedusaResponse
) => {
const { result } = await createBrandWorkflow(req.scope)
.run({
input: req.body,
})
res.json({ brand: result })
}
```
This adds a `POST` API route at `/admin/brands`. In the API route's handler, you execute the `createBrandWorkflow`, passing it the request body as input.
You return in the response the created brand.
<Note> <Note>
@@ -56,11 +31,151 @@ Learn more about API routes [in this guide](../../../basics/api-routes/page.mdx)
</Note> </Note>
The route's path is the path of `route.{ts,js}` relative to `src/api`. So, to create the API route at `/admin/brands`, create the file `src/api/admin/brands/route.ts` with the following content:
![Directory structure of the Medusa application after adding the route](https://res.cloudinary.com/dza7lstvk/image/upload/v1732869882/Medusa%20Book/brand-route-dir-overview-2_hjqlnf.jpg)
```ts title="src/api/admin/brands/route.ts"
import {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import {
createBrandWorkflow,
} from "../../../workflows/create-brand"
type PostAdminCreateBrandType = {
name: string
}
export const POST = async (
req: MedusaRequest<PostAdminCreateBrandType>,
res: MedusaResponse
) => {
const { result } = await createBrandWorkflow(req.scope)
.run({
input: req.validatedBody,
})
res.json({ brand: result })
}
```
You export a route handler function with its name (`POST`) being the HTTP method of the API route you're exposing.
The function receives two parameters: a `MedusaRequest` object to access request details, and `MedusaResponse` object to return or manipulate the response. The `MedusaRequest` object's `scope` property is the [Medusa container](../../../basics/medusa-container/page.mdx) that holds framework tools and custom and core modules' services.
<Note title="Tip">
`MedusaRequest` accepts the request body's type as a type argument.
</Note>
In the API route's handler, you execute the `createBrandWorkflow` by invoking it and passing the Medusa container `req.scope` as a parameter, then invoking its `run` method. You pass the workflow's input in the `input` property of the `run` method's parameter. You pass the request body's parameters using the `validatedBody` property of `MedusaRequest`.
You return a JSON response with the created brand using the `res.json` method.
---
## 2. Create Validation Schema
The API route you created accepts the brand's name in the request body. So, you'll create a schema used to validate incoming request body parameters.
Medusa uses [Zod](https://zod.dev/) to create validation schemas. These schemas are then used to validate incoming request bodies or query parameters.
<Note>
Learn more about API route validation in [this chapter](../../../advanced-development/api-routes/validation/page.mdx).
</Note>
You create a validation schema in a TypeScript or JavaScript file under a sub-directory of the `src/api` directory. So, create the file `src/api/admin/brands/validators.ts` with the following content:
![Directory structure of Medusa application after adding validators file](https://res.cloudinary.com/dza7lstvk/image/upload/v1732869806/Medusa%20Book/brand-route-dir-overview-1_yfyjss.jpg)
```ts title="src/api/admin/brands/validators.ts"
import { z } from "zod"
export const PostAdminCreateBrand = z.object({
name: z.string()
})
```
You export a validation schema that expects in the request body an object having a `name` property whose value is a string.
You can then replace `PostAdminCreateBrandType` in `src/api/admin/brands/route.ts` with the following:
```ts title="src/api/admin/brands/route.ts"
// ...
import { z } from "zod"
import { PostAdminCreateBrand } from "./validators"
type PostAdminCreateBrandType = z.infer<typeof PostAdminCreateBrand>
// ...
```
---
## 3. Add Validation Middleware
A middleware is a function executed before the route handler when a request is sent to an API Route. It's useful to guard API routes, parse custom request body types, and apply validation on an API route.
<Note>
Learn more about middlewares in [this chapter](../../../advanced-development/api-routes/middlewares/page.mdx).
</Note>
Medusa provides a `validateAndTransformBody` middleware that accepts a Zod validation schema and returns a response error if a request is sent with body parameters that don't satisfy the validation schema.
Middlewares are defined in the special file `src/api/middlewares.ts`. So, to add the validation middleware on the API route you created in the previous step, create the file `src/api/middlewares.ts` with the following content:
![Directory structure of the Medusa application after adding the middleware](https://res.cloudinary.com/dza7lstvk/image/upload/v1732869977/Medusa%20Book/brand-route-dir-overview-3_kcx511.jpg)
```ts title="src/api/middlewares.ts"
import {
defineMiddlewares,
validateAndTransformBody,
} from "@medusajs/framework/http"
import { PostAdminCreateBrand } from "./admin/brands/validators"
export default defineMiddlewares({
routes: [
{
matcher: "/admin/brands",
method: "POST",
middlewares: [
validateAndTransformBody(PostAdminCreateBrand),
],
},
],
})
```
You define the middlewares using the `defineMiddlewares` function and export its returned value. The function accepts an object having a `routes` property, which is an array of middleware objects.
In the middleware object, you define three properties:
- `matcher`: a string or regular expression indicating the API route path to apply the middleware on. You pass the create brand's route `/admin/brand`.
- `method`: The HTTP method to restrict the middleware to, which is `POST`.
- `middlewares`: An array of middlewares to apply on the route. You pass the `validateAndTransformBody` middleware, passing it the Zod schema you created earlier.
The Medusa application will now validate the body parameters of `POST` requests sent to `/admin/brands` to ensure they match the Zod validation schema. If not, an error is returned in the response specifying the issues to fix in the request body.
--- ---
## Test API Route ## Test API Route
To test it out, first, retrieve an authenticated token of your admin user by sending a `POST` request to the `/auth/user/emailpass` API Route: To test out the API route, start the Medusa application with the following command:
```bash npm2yarn
npm run dev
```
Since the `/admin/brands` API route has a `/admin` prefix, it's only accessible by authenticated admin users.
So, to retrieve an authenticated token of your admin user, send a `POST` request to the `/auth/user/emailpass` API Route:
```bash ```bash
curl -X POST 'http://localhost:9000/auth/user/emailpass' \ curl -X POST 'http://localhost:9000/auth/user/emailpass' \
@@ -71,7 +186,13 @@ curl -X POST 'http://localhost:9000/auth/user/emailpass' \
}' }'
``` ```
Make sure to replace the email and password with your user's credentials. Make sure to replace the email and password with your admin user's credentials.
<Note title="Tip">
Don't have an admin user? Refer to [this guide](../../../installation/page.mdx#create-medusa-admin-user).
</Note>
Then, send a `POST` request to `/admin/brands`, passing the token received from the previous request in the `Authorization` header: Then, send a `POST` request to `/admin/brands`, passing the token received from the previous request in the `Authorization` header:
@@ -101,14 +222,16 @@ This returns the created brand in the response:
## Summary ## Summary
By following the previous example chapters, you implemented a custom feature that allows admin users to create a brand by: By following the previous example chapters, you implemented a custom feature that allows admin users to create a brand. You did that by:
1. Creating a module that defines and manages the `Brand` data model. 1. Creating a module that defines and manages a `brand` table in the database.
2. Creating a workflow that uses the module's main service to create a brand record, and implements the compensation logic to delete that brand in case an error occurs. 2. Creating a workflow that uses the module's service to create a brand record, and implements the compensation logic to delete that brand in case an error occurs.
3. Creating an API route that allows admin users to create a brand. 3. Creating an API route that allows admin users to create a brand.
--- ---
## Next Steps ## Next Steps: Associate Brand with Product
In the next chapters, you'll learn how to extend data models and associate the brand with a product. Now that you have brands in your Medusa application, you want to associate a brand with a product, which is defined in the [Product Module](!resources!/commerce-modules/product).
In the next chapters, you'll learn how to build associations between data models defined in different modules.
@@ -8,7 +8,7 @@ In this chapter, you'll build a Brand Module that adds a `brand` table to the da
A module is a reusable package of functionalities related to a single domain or integration. Medusa comes with multiple pre-built modules for core commerce needs, such as the [Cart Module](!resources!/commerce-modules/cart) that holds the data models and business logic for cart operations. A module is a reusable package of functionalities related to a single domain or integration. Medusa comes with multiple pre-built modules for core commerce needs, such as the [Cart Module](!resources!/commerce-modules/cart) that holds the data models and business logic for cart operations.
You create in a module new tables in the database, and expose a class that provides data-management methods on those tables. In the next chapters, you'll see how you use the module's functionalities to expose commerce features. In a module, you create data models and business logic to manage them. In the next chapters, you'll see how you use the module to build commerce features.
<Note> <Note>
@@ -20,6 +20,8 @@ Learn more about modules in [this chapter](../../../basics/modules/page.mdx).
Modules are created in a sub-directory of `src/modules`. So, start by creating the directory `src/modules/brand` that will hold the Brand Module's files. Modules are created in a sub-directory of `src/modules`. So, start by creating the directory `src/modules/brand` that will hold the Brand Module's files.
![Directory structure in Medusa project after adding the brand directory](https://res.cloudinary.com/dza7lstvk/image/upload/v1732868844/Medusa%20Book/brand-dir-overview-1_hxwvgx.jpg)
--- ---
## 2. Create Data Model ## 2. Create Data Model
@@ -34,6 +36,8 @@ Learn more about data models in [this chapter](../../../basics/modules/page.mdx#
You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a data model that represents a new `brand` table in the database, create the file `src/modules/brand/models/brand.ts` with the following content: You create a data model in a TypeScript or JavaScript file under the `models` directory of a module. So, to create a data model that represents a new `brand` table in the database, create the file `src/modules/brand/models/brand.ts` with the following content:
![Directory structure in module after adding the brand data model](https://res.cloudinary.com/dza7lstvk/image/upload/v1732868920/Medusa%20Book/brand-dir-overview-2_lexhdl.jpg)
```ts title="src/modules/brand/models/brand.ts" ```ts title="src/modules/brand/models/brand.ts"
import { model } from "@medusajs/framework/utils" import { model } from "@medusajs/framework/utils"
@@ -72,6 +76,8 @@ Learn more about services in [this chapter](../../../basics/modules/page.mdx#2-c
You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, create the file `src/modules/brand/service.ts` with the following content: You define a service in a `service.ts` or `service.js` file at the root of your module's directory. So, create the file `src/modules/brand/service.ts` with the following content:
![Directory structure in module after adding the service](https://res.cloudinary.com/dza7lstvk/image/upload/v1732868984/Medusa%20Book/brand-dir-overview-3_jo7baj.jpg)
export const serviceHighlights = [ export const serviceHighlights = [
["4", "MedusaService", "A service factory that generates data-management methods."] ["4", "MedusaService", "A service factory that generates data-management methods."]
] ]
@@ -109,6 +115,8 @@ A module must export a definition that tells Medusa the name of the module and i
So, to export the Brand Module's definition, create the file `src/modules/brand/index.ts` with the following content: So, to export the Brand Module's definition, create the file `src/modules/brand/index.ts` with the following content:
![Directory structure in module after adding the definition file](https://res.cloudinary.com/dza7lstvk/image/upload/v1732869045/Medusa%20Book/brand-dir-overview-4_nf8ymw.jpg)
```ts title="src/modules/brand/index.ts" ```ts title="src/modules/brand/index.ts"
import { Module } from "@medusajs/framework/utils" import { Module } from "@medusajs/framework/utils"
import BrandModuleService from "./service" import BrandModuleService from "./service"
@@ -8,10 +8,10 @@ In the upcoming chapters, you'll follow step-by-step guides to build custom feat
By following these guides, you'll add brands to the Medusa application that you can associate with products. By following these guides, you'll add brands to the Medusa application that you can associate with products.
To build a custom feature in Medusa, you need three main ingredients: To build a custom feature in Medusa, you need three main tools:
- [Module](../../basics/modules/page.mdx): a re-usable package that defines commerce functionalities for a single domain. It defines new tables to add to the database, and a class of methods to manage these tables. - [Module](../../basics/modules/page.mdx): a package with commerce logic for a single domain. It defines new tables to add to the database, and a class of methods to manage these tables.
- [Workflow](../../basics/workflows/page.mdx): a special function that performs a task in a series of steps with advanced features like roll-back mechanism and retry configurations. The steps of a workflow use functionalities implemented by modules. - [Workflow](../../basics/workflows/page.mdx): a tool to perform an operation comprising multiple steps with built-in rollback and retry mechanisms.
- [API route](../../basics/api-routes/page.mdx): a REST endpoint that exposes commerce features to clients, such as the admin dashboard or a storefront. The API route executes a workflow that implements the commerce feature using modules. - [API route](../../basics/api-routes/page.mdx): a REST endpoint that exposes commerce features to clients, such as the admin dashboard or a storefront. The API route executes a workflow that implements the commerce feature using modules.
![Diagram showcasing the flow of a custom developed feature](https://res.cloudinary.com/dza7lstvk/image/upload/v1725867628/Medusa%20Book/custom-development_nofvp6.jpg) ![Diagram showcasing the flow of a custom developed feature](https://res.cloudinary.com/dza7lstvk/image/upload/v1725867628/Medusa%20Book/custom-development_nofvp6.jpg)
@@ -6,9 +6,9 @@ export const metadata = {
# {metadata.title} # {metadata.title}
This chapter is a follow-up to the [previous one](../module/page.mdx) where you created a Brand Module. In this chapter, you'll create a workflow that creates a brand. This chapter builds on the work from the [previous chapter](../module/page.mdx) where you created a Brand Module.
You implement commerce features within workflows. A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow similar to a regular function, but it's a special function that allows you to define roll-back logic, retry configurations, and more advanced features. After adding custom brands to your application, you build commerce features around them using workflows. A workflow is a series of queries and actions, called steps, that complete a task spanning across modules. You construct a workflow similar to a regular function, but it's a special function that allows you to define roll-back logic, retry configurations, and more advanced features.
The workflow you'll create in this chapter will use the Brand Module's service to implement the feature of creating a brand. In the [next chapter](../api-route/page.mdx), you'll expose an API route that allows admin users to create a brand, and you'll use this workflow in the route's implementation. The workflow you'll create in this chapter will use the Brand Module's service to implement the feature of creating a brand. In the [next chapter](../api-route/page.mdx), you'll expose an API route that allows admin users to create a brand, and you'll use this workflow in the route's implementation.
@@ -33,9 +33,11 @@ Learn more about workflows in [this chapter](../../../basics/workflows/page.mdx)
A workflow consists of a series of steps, each step created in a TypeScript or JavaScript file under the `src/workflows` directory. A step is defined using the `createStep` utility function imported from `@medusajs/framework/workflows-sdk`. A workflow consists of a series of steps, each step created in a TypeScript or JavaScript file under the `src/workflows` directory. A step is defined using the `createStep` utility function imported from `@medusajs/framework/workflows-sdk`.
The workflow you're creating in this guide has one step to create the brand. So, create the file `src/workflows/create-brand/steps/create-brand.ts` with the following content: The workflow you're creating in this guide has one step to create the brand. So, create the file `src/workflows/create-brand.ts` with the following content:
```ts title="src/workflows/create-brand/steps/create-brand.ts" ![Directory structure in the Medusa project after adding the file for createBrandStep](https://res.cloudinary.com/dza7lstvk/image/upload/v1732869184/Medusa%20Book/brand-workflow-dir-overview-1_fjvf5j.jpg)
```ts title="src/workflows/create-brand.ts"
import { import {
createStep, createStep,
StepResponse, StepResponse,
@@ -89,7 +91,7 @@ Learn more about the compensation function in [this chapter](../../../advanced-d
To add a compensation function to the `createBrandStep`, pass it as a third parameter to `createStep`: To add a compensation function to the `createBrandStep`, pass it as a third parameter to `createStep`:
```ts title="src/workflows/create-brand/steps/create-brand.ts" ```ts title="src/workflows/create-brand.ts"
export const createBrandStep = createStep( export const createBrandStep = createStep(
// ... // ...
async (id: string, { container }) => { async (id: string, { container }) => {
@@ -120,22 +122,25 @@ So, if an error occurs during the workflow's execution, the brand that was creat
You can now create the workflow that runs the `createBrandStep`. A workflow is created in a TypeScript or JavaScript file under the `src/workflows` directory. In the file, you use the `createWorkflow` function imported from `@medusajs/framework/workflows-sdk` to create the workflow. You can now create the workflow that runs the `createBrandStep`. A workflow is created in a TypeScript or JavaScript file under the `src/workflows` directory. In the file, you use the `createWorkflow` function imported from `@medusajs/framework/workflows-sdk` to create the workflow.
So, create the file `src/workflows/create-brand/index.ts` with the following content: Add the following content in the same `src/workflows/create-brand.ts` file:
```ts ```ts
// other imports...
import { import {
// ...
createWorkflow, createWorkflow,
WorkflowResponse, WorkflowResponse,
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
import { createBrandStep } from "./steps/create-brand"
type CreateBrandInput = { // ...
type CreateBrandWorkflowInput = {
name: string name: string
} }
export const createBrandWorkflow = createWorkflow( export const createBrandWorkflow = createWorkflow(
"create-brand", "create-brand",
(input: CreateBrandInput) => { (input: CreateBrandWorkflowInput) => {
const brand = createBrandStep(input) const brand = createBrandStep(input)
return new WorkflowResponse(brand) return new WorkflowResponse(brand)
+1 -1
View File
@@ -89,7 +89,7 @@ export const generatedEditDates = {
"app/learn/advanced-development/api-routes/additional-data/page.mdx": "2024-09-30T08:43:53.120Z", "app/learn/advanced-development/api-routes/additional-data/page.mdx": "2024-09-30T08:43:53.120Z",
"app/learn/advanced-development/workflows/page.mdx": "2024-09-18T08:00:57.364Z", "app/learn/advanced-development/workflows/page.mdx": "2024-09-18T08:00:57.364Z",
"app/learn/advanced-development/workflows/variable-manipulation/page.mdx": "2024-11-14T16:11:24.538Z", "app/learn/advanced-development/workflows/variable-manipulation/page.mdx": "2024-11-14T16:11:24.538Z",
"app/learn/customization/custom-features/api-route/page.mdx": "2024-09-12T12:42:34.201Z", "app/learn/customization/custom-features/api-route/page.mdx": "2024-11-28T13:12:10.521Z",
"app/learn/customization/custom-features/module/page.mdx": "2024-11-28T09:25:29.098Z", "app/learn/customization/custom-features/module/page.mdx": "2024-11-28T09:25:29.098Z",
"app/learn/customization/custom-features/workflow/page.mdx": "2024-11-28T10:47:28.084Z", "app/learn/customization/custom-features/workflow/page.mdx": "2024-11-28T10:47:28.084Z",
"app/learn/customization/extend-models/create-links/page.mdx": "2024-09-30T08:43:53.133Z", "app/learn/customization/extend-models/create-links/page.mdx": "2024-09-30T08:43:53.133Z",
+1 -1
View File
@@ -108,7 +108,7 @@ export const sidebar = numberSidebarItems(
}, },
{ {
type: "link", type: "link",
title: "Create Brand API Route", title: "Brand API Route",
path: "/learn/customization/custom-features/api-route", path: "/learn/customization/custom-features/api-route",
}, },
], ],