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: + +![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, + 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: + +![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 + +// ... +``` + +--- + +## 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: + +![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. + + + +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. +![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" 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. ![Diagram showcasing the flow of a custom developed feature](https://res.cloudinary.com/dza7lstvk/image/upload/v1725867628/Medusa%20Book/custom-development_nofvp6.jpg) 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" +![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) 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", }, ],