275 lines
9.0 KiB
Plaintext
275 lines
9.0 KiB
Plaintext
export const metadata = {
|
||
title: `${pageNumber} Modules`,
|
||
}
|
||
|
||
# {metadata.title}
|
||
|
||
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:
|
||
|
||
```ts title="src/modules/hello/models/my-custom.ts"
|
||
import { model } from "@medusajs/framework/utils"
|
||
|
||
const MyCustom = model.define("my_custom", {
|
||
id: model.id().primaryKey(),
|
||
name: model.text(),
|
||
})
|
||
|
||
export 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`.
|
||
|
||
<Note>
|
||
|
||
Data models automatically have the date properties `created_at`, `updated_at`, and `deleted_at`.
|
||
|
||
</Note>
|
||
|
||
### 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:
|
||
|
||
export const highlights = [
|
||
["4", "MedusaService", "The service factory function."],
|
||
["5", "MyCustom", "The data models to generate data-management methods for."]
|
||
]
|
||
|
||
```ts title="src/modules/hello/service.ts" highlights={highlights}
|
||
import { MedusaService } from "@medusajs/framework/utils"
|
||
import MyCustom from "./models/my-custom"
|
||
|
||
class HelloModuleService extends MedusaService({
|
||
MyCustom,
|
||
}){
|
||
}
|
||
|
||
export 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.
|
||
|
||
<Note title="Tip">
|
||
|
||
If a module doesn't have data models, it doesn't need to extend `MedusaService`.
|
||
|
||
</Note>
|
||
|
||
<Note>
|
||
|
||
You'll learn more about the methods generated by the service factory in later chapters.
|
||
|
||
</Note>
|
||
|
||
### 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:
|
||
|
||
```ts title="src/modules/hello/index.ts" highlights={[["7", "", "The main service of the module."]]}
|
||
import HelloModuleService from "./service"
|
||
import { Module } from "@medusajs/framework/utils"
|
||
|
||
export const HELLO_MODULE = "helloModuleService"
|
||
|
||
export default Module(HELLO_MODULE, {
|
||
service: HelloModuleService,
|
||
})
|
||
```
|
||
|
||
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:
|
||
|
||
```js title="medusa-config.js" highlights={[["6", "helloModuleService", "The key of the main service to be registered in the Medusa container."]]}
|
||
module.exports = defineConfig({
|
||
projectConfig: {
|
||
// ...
|
||
},
|
||
modules: {
|
||
helloModuleService: {
|
||
resolve: "./modules/hello",
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
The key `helloModuleService` is the name of the module’s main service. It will be registered in the Medusa container with that name.
|
||
|
||
<Note>
|
||
|
||
The key must be the same value passed as the first parameter to the `Module` function in the module's definition.
|
||
|
||
</Note>
|
||
|
||
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:
|
||
|
||
```bash
|
||
npx medusa db:generate helloModuleService
|
||
```
|
||
|
||
The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for.
|
||
|
||
<Note>
|
||
|
||
The module name `helloModuleService` is the key used when registering the module in Medusa's `modules` configuration.
|
||
|
||
</Note>
|
||
|
||
The above command creates a migration file at the directory `src/modules/hello/migrations` similar to the following:
|
||
|
||
```ts
|
||
import { Migration } from "@mikro-orm/migrations"
|
||
|
||
export class Migration20240702105919 extends Migration {
|
||
|
||
async up(): Promise<void> {
|
||
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\"));")
|
||
}
|
||
|
||
async down(): Promise<void> {
|
||
this.addSql("drop table if exists \"my_custom\" cascade;")
|
||
}
|
||
|
||
}
|
||
```
|
||
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```ts title="src/api/custom/route.ts"
|
||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||
import HelloModuleService from "../../modules/hello/service"
|
||
import { HELLO_MODULE } from "../../modules/hello"
|
||
|
||
export async function GET(
|
||
req: MedusaRequest,
|
||
res: MedusaResponse
|
||
): Promise<void> {
|
||
const helloModuleService: HelloModuleService = req.scope.resolve(
|
||
HELLO_MODULE
|
||
)
|
||
|
||
const my_custom = await helloModuleService.createMyCustoms({
|
||
name: "test"
|
||
})
|
||
|
||
res.json({
|
||
my_custom
|
||
})
|
||
}
|
||
```
|
||
|
||
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:
|
||
|
||
```bash npm2yarn
|
||
npm run dev
|
||
```
|
||
|
||
Finally, send a `GET` request to `/custom`:
|
||
|
||
```bash
|
||
curl http://localhost:9000/custom
|
||
```
|
||
|
||
You’ll receive the following response:
|
||
|
||
```json
|
||
{
|
||
"my_custom": {
|
||
"id": "123...",
|
||
"name": "test",
|
||
"created_at": "...",
|
||
"updated_at": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 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.
|
||
|
||
<Note title="Use modules when" type="success">
|
||
|
||
- 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.
|
||
|
||
</Note>
|