diff --git a/www/apps/book/app/learn/customization/custom-features/api-route/page.mdx b/www/apps/book/app/learn/customization/custom-features/api-route/page.mdx
index 4626295662..f4d424556b 100644
--- a/www/apps/book/app/learn/customization/custom-features/api-route/page.mdx
+++ b/www/apps/book/app/learn/customization/custom-features/api-route/page.mdx
@@ -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}
-
+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.
-
+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.
-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,
- 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…).
@@ -56,11 +31,151 @@ Learn more about API routes [in this guide](../../../basics/api-routes/page.mdx)
+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:
+
+
+
+```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,
+ 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.
+
+
+
+`MedusaRequest` accepts the request body's type as a type argument.
+
+
+
+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.
+
+
+
+Learn more about API route validation in [this chapter](../../../advanced-development/api-routes/validation/page.mdx).
+
+
+
+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:
+
+
+
+```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
+
+// ...
+```
+
+---
+
+## 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.
+
+
+
+Learn more about middlewares in [this chapter](../../../advanced-development/api-routes/middlewares/page.mdx).
+
+
+
+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:
+
+
+
+```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.
+
+
+
+Don't have an admin user? Refer to [this guide](../../../installation/page.mdx#create-medusa-admin-user).
+
+
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.
diff --git a/www/apps/book/app/learn/customization/custom-features/module/page.mdx b/www/apps/book/app/learn/customization/custom-features/module/page.mdx
index 1828510dce..07cd352b31 100644
--- a/www/apps/book/app/learn/customization/custom-features/module/page.mdx
+++ b/www/apps/book/app/learn/customization/custom-features/module/page.mdx
@@ -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.
@@ -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.
+
+
---
## 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:
+
+
```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:
+
+
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:
+
+
```ts title="src/modules/brand/index.ts"
import { Module } from "@medusajs/framework/utils"
import BrandModuleService from "./service"
diff --git a/www/apps/book/app/learn/customization/custom-features/page.mdx b/www/apps/book/app/learn/customization/custom-features/page.mdx
index 47622a66c9..3b3a1268c6 100644
--- a/www/apps/book/app/learn/customization/custom-features/page.mdx
+++ b/www/apps/book/app/learn/customization/custom-features/page.mdx
@@ -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.

diff --git a/www/apps/book/app/learn/customization/custom-features/workflow/page.mdx b/www/apps/book/app/learn/customization/custom-features/workflow/page.mdx
index 79f0d68a3b..3d5b40bd3f 100644
--- a/www/apps/book/app/learn/customization/custom-features/workflow/page.mdx
+++ b/www/apps/book/app/learn/customization/custom-features/workflow/page.mdx
@@ -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"
+
+
+```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)
diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs
index 1924dae459..d536e4c167 100644
--- a/www/apps/book/generated/edit-dates.mjs
+++ b/www/apps/book/generated/edit-dates.mjs
@@ -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",
diff --git a/www/apps/book/sidebar.mjs b/www/apps/book/sidebar.mjs
index 2351f36262..fd0616167d 100644
--- a/www/apps/book/sidebar.mjs
+++ b/www/apps/book/sidebar.mjs
@@ -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",
},
],