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"
export const metadata = {
title: `${pageNumber} Create Brand API Route`,
title: `${pageNumber} Guide: Create Brand API Route`,
}
# {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
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"
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.
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…).
<Note>
@@ -56,11 +31,151 @@ Learn more about API routes [in this guide](../../../basics/api-routes/page.mdx)
</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
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
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:
@@ -101,14 +222,16 @@ This returns the created brand in the response:
## 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.
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.
1. Creating a module that defines and manages a `brand` table in the database.
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.
---
## 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.
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>
@@ -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.
![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
@@ -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:
![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"
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:
![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 = [
["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:
![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"
import { Module } from "@medusajs/framework/utils"
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.
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.
- [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.
- [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 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.
![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}
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.
@@ -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`.
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 {
createStep,
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`:
```ts title="src/workflows/create-brand/steps/create-brand.ts"
```ts title="src/workflows/create-brand.ts"
export const createBrandStep = createStep(
// ...
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.
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
// other imports...
import {
// ...
createWorkflow,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
import { createBrandStep } from "./steps/create-brand"
type CreateBrandInput = {
// ...
type CreateBrandWorkflowInput = {
name: string
}
export const createBrandWorkflow = createWorkflow(
"create-brand",
(input: CreateBrandInput) => {
(input: CreateBrandWorkflowInput) => {
const brand = createBrandStep(input)
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/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/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/workflow/page.mdx": "2024-11-28T10:47:28.084Z",
"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",
title: "Create Brand API Route",
title: "Brand API Route",
path: "/learn/customization/custom-features/api-route",
},
],