- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
Medusa Examples
This documentation page has examples of customizations useful for your custom development in the Medusa application.
Each section links to the associated documentation page to learn more about it.
API Routes#
An API route is a REST API endpoint that exposes commerce features to external applications, such as storefronts, the admin dashboard, or third-party systems.
Create API Route#
Create the file src/api/hello-world/route.ts
with the following content:
This creates a GET
API route at /hello-world
.
Learn more in this documentation.
Resolve Resources in API Route#
To resolve resources from the Medusa container in an API route:
1import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"2import { Modules } from "@medusajs/framework/utils"3 4export const GET = async (5 req: MedusaRequest, 6 res: MedusaResponse7) => {8 const productModuleService = req.scope.resolve(9 Modules.PRODUCT10 )11 12 const [, count] = await productModuleService13 .listAndCountProducts()14 15 res.json({16 count,17 })18}
This resolves the Product Module's main service.
Learn more in this documentation.
Use Path Parameters#
API routes can accept path parameters.
To do that, create the file src/api/hello-world/[id]/route.ts
with the following content:
Learn more about path parameters in this documentation.
Use Query Parameters#
API routes can accept query parameters:
Learn more about query parameters in this documentation.
Use Body Parameters#
API routes can accept request body parameters:
Learn more about request body parameters in this documentation.
Set Response Code#
You can change the response code of an API route:
Learn more about setting the response code in this documentation.
Execute a Workflow in an API Route#
To execute a workflow in an API route:
1import type {2 MedusaRequest,3 MedusaResponse,4} from "@medusajs/framework/http"5import myWorkflow from "../../workflows/hello-world"6 7export async function GET(8 req: MedusaRequest,9 res: MedusaResponse10) {11 const { result } = await myWorkflow(req.scope)12 .run({13 input: {14 name: req.query.name as string,15 },16 })17 18 res.send(result)19}
Learn more in this documentation.
Change Response Content Type#
By default, an API route's response has the content type application/json
.
To change it to another content type, use the writeHead
method of MedusaResponse
:
1import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"2 3export const GET = async (4 req: MedusaRequest,5 res: MedusaResponse6) => {7 res.writeHead(200, {8 "Content-Type": "text/event-stream",9 "Cache-Control": "no-cache",10 Connection: "keep-alive",11 })12 13 const interval = setInterval(() => {14 res.write("Streaming data...\n")15 }, 3000)16 17 req.on("end", () => {18 clearInterval(interval)19 res.end()20 })21}
This changes the response type to return an event stream.
Learn more in this documentation.
Create Middleware#
A middleware is a function executed when a request is sent to an API Route.
Create the file src/api/middlewares.ts
with the following content:
1import { defineMiddlewares } from "@medusajs/medusa"2import type { 3 MedusaNextFunction, 4 MedusaRequest, 5 MedusaResponse, 6} from "@medusajs/framework/http"7 8export default defineMiddlewares({9 routes: [10 {11 matcher: "/custom*",12 middlewares: [13 (14 req: MedusaRequest, 15 res: MedusaResponse, 16 next: MedusaNextFunction17 ) => {18 console.log("Received a request!")19 20 next()21 },22 ],23 },24 {25 matcher: "/custom/:id",26 middlewares: [27 (28 req: MedusaRequest, 29 res: MedusaResponse, 30 next: MedusaNextFunction31 ) => {32 console.log("With Path Parameter")33 34 next()35 },36 ],37 },38 ],39})
Learn more about middlewares in this documentation.
Restrict HTTP Methods in Middleware#
To restrict a middleware to an HTTP method:
1import { defineMiddlewares } from "@medusajs/medusa"2import type { 3 MedusaNextFunction, 4 MedusaRequest, 5 MedusaResponse, 6} from "@medusajs/framework/http"7 8export default defineMiddlewares({9 routes: [10 {11 matcher: "/custom*",12 method: ["POST", "PUT"],13 middlewares: [14 // ...15 ],16 },17 ],18})
Add Validation for Custom Routes#
- Create a Zod schema in the file
src/api/custom/validators.ts
:
- Add a validation middleware to the custom route in
src/api/middlewares.ts
:
1import { defineMiddlewares } from "@medusajs/medusa"2import { validateAndTransformBody } from "@medusajs/framework/utils"3import { PostStoreCustomSchema } from "./custom/validators"4 5export default defineMiddlewares({6 routes: [7 {8 matcher: "/custom",9 method: "POST",10 middlewares: [11 validateAndTransformBody(PostStoreCustomSchema),12 ],13 },14 ],15})
- Use the validated body in the
/custom
API route:
1import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"2import { z } from "zod"3import { PostStoreCustomSchema } from "./validators"4 5type PostStoreCustomSchemaType = z.infer<6 typeof PostStoreCustomSchema7>8 9export const POST = async (10 req: MedusaRequest<PostStoreCustomSchemaType>,11 res: MedusaResponse12) => {13 res.json({14 sum: req.validatedBody.a + req.validatedBody.b,15 })16}
Learn more about request body validation in this documentation.
Pass Additional Data to API Route#
In this example, you'll pass additional data to the Create Product API route, then consume its hook:
- Create the file
src/api/middlewares.ts
with the following content:
- Create the file
src/workflows/hooks/created-product.ts
with the following content:
1import { createProductsWorkflow } from "@medusajs/medusa/core-flows"2import { StepResponse } from "@medusajs/framework/workflows-sdk"3 4createProductsWorkflow.hooks.productsCreated(5 (async ({ products, additional_data }, { container }) => {6 if (!additional_data.brand_id) {7 return new StepResponse([], [])8 }9 10 // TODO perform custom action11 }),12 (async (links, { container }) => {13 // TODO undo the action in the compensation14 })15 16)
Restrict an API Route to Admin Users#
You can protect API routes by restricting access to authenticated admin users only.
Add the following middleware in src/api/middlewares.ts
:
Learn more in this documentation.
Restrict an API Route to Logged-In Customers#
You can protect API routes by restricting access to authenticated customers only.
Add the following middleware in src/api/middlewares.ts
:
Learn more in this documentation.
Retrieve Logged-In Admin User#
To retrieve the currently logged-in user in an API route:
1import type {2 AuthenticatedMedusaRequest,3 MedusaResponse,4} from "@medusajs/framework/http"5import { Modules } from "@medusajs/framework/utils"6 7export const GET = async (8 req: AuthenticatedMedusaRequest,9 res: MedusaResponse10) => {11 const userModuleService = req.scope.resolve(12 Modules.USER13 )14 15 const user = await userModuleService.retrieveUser(16 req.auth_context.actor_id17 )18 19 // ...20}
Learn more in this documentation.
Retrieve Logged-In Customer#
To retrieve the currently logged-in customer in an API route:
1import type {2 AuthenticatedMedusaRequest,3 MedusaResponse,4} from "@medusajs/framework/http"5import { Modules } from "@medusajs/framework/utils"6 7export const GET = async (8 req: AuthenticatedMedusaRequest,9 res: MedusaResponse10) => {11 if (req.auth_context?.actor_id) {12 // retrieve customer13 const customerModuleService = req.scope.resolve(14 Modules.CUSTOMER15 )16 17 const customer = await customerModuleService.retrieveCustomer(18 req.auth_context.actor_id19 )20 }21 22 // ...23}
Learn more in this documentation.
Throw Errors in API Route#
To throw errors in an API route, use the MedusaError
utility:
1import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"2import { MedusaError } from "@medusajs/framework/utils"3 4export const GET = async (5 req: MedusaRequest,6 res: MedusaResponse7) => {8 if (!req.query.q) {9 throw new MedusaError(10 MedusaError.Types.INVALID_DATA,11 "The `q` query parameter is required."12 )13 }14 15 // ...16}
Learn more in this documentation.
Override Error Handler of API Routes#
To override the error handler of API routes, create the file src/api/middlewares.ts
with the following content:
1import { 2 defineMiddlewares, 3 MedusaNextFunction, 4 MedusaRequest, 5 MedusaResponse,6} from "@medusajs/framework/http"7import { MedusaError } from "@medusajs/framework/utils"8 9export default defineMiddlewares({10 errorHandler: (11 error: MedusaError | any, 12 req: MedusaRequest, 13 res: MedusaResponse, 14 next: MedusaNextFunction15 ) => {16 res.status(400).json({17 error: "Something happened.",18 })19 },20})
Learn more in this documentation,
Setting up CORS for Custom API Routes#
By default, Medusa configures CORS for all routes starting with /admin
, /store
, and /auth
.
To configure CORS for routes under other prefixes, create the file src/api/middlewares.ts
with the following content:
1import { defineMiddlewares } from "@medusajs/medusa"2import type { 3 MedusaNextFunction, 4 MedusaRequest, 5 MedusaResponse, 6} from "@medusajs/framework/http"7import { ConfigModule } from "@medusajs/framework/types"8import { parseCorsOrigins } from "@medusajs/framework/utils"9import cors from "cors"10 11export default defineMiddlewares({12 routes: [13 {14 matcher: "/custom*",15 middlewares: [16 (17 req: MedusaRequest, 18 res: MedusaResponse, 19 next: MedusaNextFunction20 ) => {21 const configModule: ConfigModule =22 req.scope.resolve("configModule")23 24 return cors({25 origin: parseCorsOrigins(26 configModule.projectConfig.http.storeCors27 ),28 credentials: true,29 })(req, res, next)30 },31 ],32 },33 ],34})
Parse Webhook Body#
By default, the Medusa application parses a request's body using JSON.
To parse a webhook's body, create the file src/api/middlewares.ts
with the following content:
To access the raw body data in your route, use the req.rawBody
property:
Modules#
A module is a package of reusable commerce or architectural functionalities. They handle business logic in a class called a service, and define and manage data models that represent tables in the database.
Create Module#
- Create the directory
src/modules/hello
. - Create the file
src/modules/hello/models/my-custom.ts
with the following data model:
- Create the file
src/modules/hello/service.ts
with the following service:
- Create the file
src/modules/hello/index.ts
that exports the module definition:
- Add the module to the configurations in
medusa-config.ts
:
- Generate and run migrations:
- Use the module's main service in an API route:
1import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"2import HelloModuleService from "../../modules/hello/service"3import { HELLO_MODULE } from "../../modules/hello"4 5export async function GET(6 req: MedusaRequest,7 res: MedusaResponse8): Promise<void> {9 const helloModuleService: HelloModuleService = req.scope.resolve(10 HELLO_MODULE11 )12 13 const my_custom = await helloModuleService.createMyCustoms({14 name: "test",15 })16 17 res.json({18 my_custom,19 })20}
Module with Multiple Services#
To add services in your module other than the main one, create them in the services
directory of the module.
For example, create the file src/modules/hello/services/custom.ts
with the following content:
Then, export the service in the file src/modules/hello/services/index.ts
:
Finally, resolve the service in your module's main service or loader:
1import { MedusaService } from "@medusajs/framework/utils"2import MyCustom from "./models/my-custom"3import { CustomService } from "./services"4 5type InjectedDependencies = {6 customService: CustomService7}8 9class HelloModuleService extends MedusaService({10 MyCustom,11}){12 private customService: CustomService13 14 constructor({ customService }: InjectedDependencies) {15 super(...arguments)16 17 this.customService = customService18 }19}20 21export default HelloModuleService
Learn more in this documentation.
Accept Module Options#
A module can accept options for configurations and secrets.
To accept options in your module:
- Pass options to the module in
medusa-config.ts
:
- Access the options in the module's main service:
1import { MedusaService } from "@medusajs/framework/utils"2import MyCustom from "./models/my-custom"3 4// recommended to define type in another file5type ModuleOptions = {6 apiKey?: boolean7}8 9export default class HelloModuleService extends MedusaService({10 MyCustom,11}){12 protected options_: ModuleOptions13 14 constructor({}, options?: ModuleOptions) {15 super(...arguments)16 17 this.options_ = options || {18 apiKey: false,19 }20 }21 22 // ...23}
Learn more in this documentation.
Integrate Third-Party System in Module#
An example of integrating a dummy third-party system in a module's service:
1import { Logger } from "@medusajs/framework/types"2import { BRAND_MODULE } from ".."3 4export type ModuleOptions = {5 apiKey: string6}7 8type InjectedDependencies = {9 logger: Logger10}11 12export class BrandClient {13 private options_: ModuleOptions14 private logger_: Logger15 16 constructor(17 { logger }: InjectedDependencies, 18 options: ModuleOptions19 ) {20 this.logger_ = logger21 this.options_ = options22 }23 24 private async sendRequest(url: string, method: string, data?: any) {25 this.logger_.info(`Sending a ${26 method27 } request to ${url}. data: ${JSON.stringify(data, null, 2)}`)28 this.logger_.info(`Client Options: ${29 JSON.stringify(this.options_, null, 2)30 }`)31 }32}
Find a longer example of integrating a third-party service in this documentation.
Data Models#
A data model represents a table in the database. Medusa provides a data model language to intuitively create data models.
Create Data Model#
To create a data model in a module:
- Create the file
src/modules/hello/models/my-custom.ts
with the following data model:
- Generate and run migrations:
Learn more in this documentation.
Data Model Property Types#
A data model can have properties of the following types:
- ID property:
- Text property:
- Number property:
- Big Number property:
- Boolean property:
- Enum property:
- Date-Time property:
- JSON property:
- Array property:
Learn more in this documentation.
Set Primary Key#
To set an id
property as the primary key of a data model:
To set a text
property as the primary key:
To set a number
property as the primary key:
Learn more in this documentation.
Default Property Value#
To set the default value of a property:
Learn more in this documentation.
Nullable Property#
To allow null
values for a property:
Learn more in this documentation.
Unique Property#
To create a unique index on a property:
Learn more in this documentation.
Define Database Index on Property#
To define a database index on a property:
Learn more in this documentation.
Define Composite Index on Data Model#
To define a composite index on a data model:
1import { model } from "@medusajs/framework/utils"2 3const MyCustom = model.define("my_custom", {4 id: model.id().primaryKey(),5 name: model.text(),6 age: model.number().nullable(),7}).indexes([8 {9 on: ["name", "age"],10 where: {11 age: {12 $ne: null,13 },14 },15 },16])17 18export default MyCustom
Learn more in this documentation.
Make a Property Searchable#
To make a property searchable using terms or keywords:
Then, to search by that property, pass the q
filter to the list
or listAndCount
generated methods of the module's main service:
helloModuleService
is the main service that the data models belong to.Learn more in this documentation.
Create One-to-One Relationship#
The following creates a one-to-one relationship between the User
and Email
data models:
Learn more in this documentation.
Create One-to-Many Relationship#
The following creates a one-to-many relationship between the Store
and Product
data models:
1import { model } from "@medusajs/framework/utils"2 3const Store = model.define("store", {4 id: model.id().primaryKey(),5 products: model.hasMany(() => Product),6})7 8const Product = model.define("product", {9 id: model.id().primaryKey(),10 store: model.belongsTo(() => Store, {11 mappedBy: "products",12 }),13})
Learn more in this documentation.
Create Many-to-Many Relationship#
The following creates a many-to-many relationship between the Order
and Product
data models:
1import { model } from "@medusajs/framework/utils"2 3const Order = model.define("order", {4 id: model.id().primaryKey(),5 products: model.manyToMany(() => Product, {6 mappedBy: "orders",7 }),8})9 10const Product = model.define("product", {11 id: model.id().primaryKey(),12 orders: model.manyToMany(() => Order, {13 mappedBy: "products",14 }),15})
Learn more in this documentation.
Configure Cascades of Data Model#
To configure cascade on a data model:
This configures the delete cascade on the Store
data model so that, when a store is delete, its products are also deleted.
Learn more in this documentation.
Manage One-to-One Relationship#
Consider you have a one-to-one relationship between Email
and User
data models, where an email belongs to a user.
To set the ID of the user that an email belongs to:
helloModuleService
is the main service that the data models belong to.