Documentation

3.3. Modules

In this chapter, you’ll learn about modules and how to create them.

What is a Module?#

A module is a package of reusable commerce or architectural functionalities.

In Medusa, modules handle business logic in a class called a service, and define and manage data models that represent tables in the database.

Out of the box, Medusa comes with multiple pre-built modules for core commerce needs. For example, the Cart Module holds the data models and business logic for cart operations.

As you learn more about Medusa, you will see that Modules are central to customizations and integrations.


How to Create a Module?#

In this section, you'll build a module that has a MyCustom data model and a service to manage that data model. You'll then use the module's service in an API route to create a record of MyCustom.

Modules are created in a sub-directory of src/modules.

For example, create the directory src/modules/hello.

1. Create Data Model#

A data model represents a table in the database. It's created in a TypeScript or JavaScript file under the module's models directory.

For example, create the file src/modules/hello/models/my-custom.ts with the following content:

src/modules/hello/models/my-custom.ts
1import { model } from "@medusajs/framework/utils"2
3const MyCustom = model.define("my_custom", {4  id: model.id().primaryKey(),5  name: model.text(),6})7
8export default MyCustom

You define the data model using the define method of the model utility imported from @medusajs/framework/utils. It accepts two parameters:

  1. The first one is the name of the data model's table in the database. It should be snake-case.
  2. The second is an object, which is the data model's schema. The schema's properties are defined using the model's methods.

The example above defines the data model MyCustom with the properties id and name.

NoteData models automatically have the date properties created_at , updated_at , and deleted_at .

2. Create Service#

A module must define a service that implements its functionalities, such as manage the records of your custom data models in the database.

A service is a TypeScript or JavaScript class defined in the service.ts file at the root of your module's directory.

For example, create the file src/modules/hello/service.ts with the following content:

src/modules/hello/service.ts
1import { MedusaService } from "@medusajs/framework/utils"2import MyCustom from "./models/my-custom"3
4class HelloModuleService extends MedusaService({5  MyCustom,6}){7}8
9export default HelloModuleService

In the snippet above, your module's service extends a class generated by the MedusaService utility function, which is the service factory.

The MedusaService function accepts as a parameter an object of data models, and returns a class with generated methods for data-management Create, Read, Update, and Delete (CRUD) operations on those data models.

For example, HelloModuleService now has a createMyCustoms method to create MyCustom records, and retrieveMyCustom to retrive a MyCustom record.

TipIf a module doesn't have data models, it doesn't need to extend MedusaService .
NoteYou'll learn more about the methods generated by the service factory in later chapters.

3. Export Module Definition#

A module must have an index.ts file in its root directory. The file exports the module's definition.

For example, create the file src/modules/hello/index.ts with the following content:

src/modules/hello/index.ts
1import HelloModuleService from "./service"2import { Module } from "@medusajs/framework/utils"3
4export const HELLO_MODULE = "helloModuleService"5
6export default Module(HELLO_MODULE, {7  service: HelloModuleService,8})

You use the Module function imported from @medusajs/framework/utils to create the module's definition. It accepts two parameters:

  1. The module's name.
  2. An object with a required property service indicating the module's main service.

4. Add Module to Configurations#

The last step is to add the module in Medusa’s configurations.

In medusa-config.js, add a modules property and pass to it your custom module:

medusa-config.js
1module.exports = defineConfig({2  projectConfig: {3    // ...4  },5  modules: {6    helloModuleService: {7      resolve: "./modules/hello",8    },9  },10})

The key helloModuleService is the name of the module’s main service. It will be registered in the Medusa container with that name.

NoteThe key must be the same value passed as the first parameter to the Module function in the module's definition.

Its value is an object having the resolve property, whose value is either a path to module's directory relative to src (it shouldn't include src in the path), or an npm package’s name.

5. Generate Migrations#

A migration is a TypeScript or JavaScript file that defines database changes made by your module, such as create the my_custom table for the MyCustom data model.

To generate a migration for the data models in your module, run the following command:

Terminal
npx medusa db:generate helloModuleService

The db:generate command of the Medusa CLI accepts one or more module names to generate the migration for.

NoteThe module name helloModuleService is the key used when registering the module in Medusa's modules configuration.

The above command creates a migration file at the directory src/modules/hello/migrations similar to the following:

Code
1import { Migration } from "@mikro-orm/migrations"2
3export class Migration20240702105919 extends Migration {4
5  async up(): Promise<void> {6    this.addSql("create table if not exists \"my_custom\" (\"id\" text not null, \"name\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"my_custom_pkey\" primary key (\"id\"));")7  }8
9  async down(): Promise<void> {10    this.addSql("drop table if exists \"my_custom\" cascade;")11  }12
13}

In the migration class, the up method creates the table my_custom and defines its columns using PostgreSQL syntax. The down method drops the table.

6. Run Migrations#

To reflect the changes in the generated migration file, run the db:migrate command:

Terminal
npx medusa db:migrate

This creates the my_custom table in the database.


Test the Module#

Since the module's main service is registered in the Medusa container, you can resolve it in other resources to use its methods.

For example, create the API route src/api/custom/route.ts with the following content:

src/api/custom/route.ts
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_custom19  })20}

You resolve the Hello Module's main service and use its generated method createMyCustoms to create a new record in the database, then return that record.

Then, start the Medusa application:

Finally, send a GET request to /custom:

Code
curl http://localhost:9000/custom

You’ll receive the following response:

Code
1{2  "my_custom": {3    "id": "123...",4    "name": "test",5    "created_at": "...",6    "updated_at": "..."7  }8}

Why Use Modules#

In digital commerce, you often need to introduce custom behavior specific to your products, industry, tech stack, or your general ways of working. In other commerce platforms, introducing custom business logic and data models requires setting up separate applications to manage these customizations.

Medusa removes this overhead by allowing you to easily write custom Modules that integrate into the Medusa application without implications on the existing setup.

Use modules when
  • You're adding a new table to the database.
  • You're extending an existing table in the database to add custom fields, which is explained in later chapters.
  • You're integrating a third-party system for commerce or architectural features, as explained in later chapters.
  • You want to re-use your custom commerce functionalities across Medusa applications or use them in other environments, such as Edge functions and Next.js apps.
Was this chapter helpful?
Edit this page