412 lines
9.7 KiB
Plaintext
412 lines
9.7 KiB
Plaintext
import { Tabs, TabsList, TabsTrigger, TabsContent, TabsContentWrapper, Badge } from "docs-ui"
|
||
|
||
export const metadata = {
|
||
title: `${pageNumber} Example: CRUD Module Service`,
|
||
}
|
||
|
||
# {metadata.title}
|
||
|
||
In this chapter, you’ll find an example of how to implement CRUD operations in your module’s service and test it out with an API route.
|
||
|
||
<Note>
|
||
|
||
The example in this chapter uses the same `hello` module with the `HelloModuleService` and `MyCustom` data model from previous chapters.
|
||
|
||
</Note>
|
||
|
||
## 1. Create Types File
|
||
|
||
Create the file `src/types/hello/my-custom.ts` with the following content:
|
||
|
||
```ts title="src/types/hello/my-custom.ts"
|
||
export type MyCustomDTO = {
|
||
id: string
|
||
name: string
|
||
}
|
||
|
||
export type CreateMyCustomDTO = {
|
||
name: string
|
||
}
|
||
|
||
export type UpdateMyCustomDTO = {
|
||
name?: string
|
||
}
|
||
|
||
```
|
||
|
||
You’ll be using these types in the service.
|
||
|
||
---
|
||
|
||
## 2. Create HelloModuleService
|
||
|
||
Create or change the service at `src/modules/hello/service.ts` with the following content:
|
||
|
||
```ts title="src/modules/hello/service.ts"
|
||
import {
|
||
ModulesSdkUtils,
|
||
InjectTransactionManager,
|
||
MedusaContext,
|
||
} from "@medusajs/utils"
|
||
import {
|
||
DAL,
|
||
ModulesSdkTypes,
|
||
Context,
|
||
} from "@medusajs/types"
|
||
import { MyCustom } from "./models/my-custom"
|
||
import {
|
||
CreateMyCustomDTO,
|
||
MyCustomDTO,
|
||
UpdateMyCustomDTO,
|
||
} from "../../types/hello/my-custom"
|
||
|
||
type InjectedDependencies = {
|
||
baseRepository: DAL.RepositoryService
|
||
myCustomService: ModulesSdkTypes.InternalModuleService<any>
|
||
}
|
||
|
||
type AllModelsDTO = {
|
||
MyCustom: {
|
||
dto: MyCustomDTO
|
||
}
|
||
}
|
||
|
||
class HelloModuleService extends ModulesSdkUtils
|
||
.abstractModuleServiceFactory<
|
||
InjectedDependencies,
|
||
MyCustomDTO,
|
||
AllModelsDTO
|
||
>(MyCustom, []) {
|
||
protected baseRepository_: DAL.RepositoryService
|
||
protected myCustomService_: ModulesSdkTypes.InternalModuleService<MyCustom>
|
||
|
||
constructor(
|
||
{ baseRepository, myCustomService }: InjectedDependencies
|
||
) {
|
||
// @ts-ignore
|
||
super(...arguments)
|
||
this.baseRepository_ = baseRepository
|
||
this.myCustomService_ = myCustomService
|
||
}
|
||
|
||
@InjectTransactionManager("baseRepository_")
|
||
async create(
|
||
data: CreateMyCustomDTO,
|
||
@MedusaContext() context: Context = {}
|
||
): Promise<MyCustomDTO> {
|
||
const myCustom = await this.myCustomService_.create(
|
||
data,
|
||
context
|
||
)
|
||
|
||
return myCustom
|
||
}
|
||
|
||
@InjectTransactionManager("baseRepository_")
|
||
async update(
|
||
id: string,
|
||
data: UpdateMyCustomDTO,
|
||
@MedusaContext() context: Context = {}
|
||
): Promise<MyCustomDTO> {
|
||
const myCustom = await this.myCustomService_.update({
|
||
...data,
|
||
id,
|
||
})
|
||
|
||
return myCustom
|
||
}
|
||
}
|
||
|
||
export default HelloModuleService
|
||
```
|
||
|
||
This creates a `HelloModuleService` that uses the service factory to generate data-management methods like `list`, `retrieve`, and `delete`.
|
||
|
||
<Note>
|
||
|
||
Refer to the full list of generated methods in [this chapter](../service-factory/page.mdx#generated-methods).
|
||
|
||
</Note>
|
||
|
||
In the service, you also implement the `create` and `update` methods that create and update a `MyCustom` record respectively.
|
||
|
||
---
|
||
|
||
## 3. Configure Module Definition
|
||
|
||
Make sure that the module definition at `src/modules/hello/index.ts` uses the container and connection loaders explained in previous chapters:
|
||
|
||
```ts title="src/modules/hello/index.ts"
|
||
import HelloModuleService from "./service"
|
||
import { ModulesSdkUtils, MikroOrmBaseRepository } from "@medusajs/utils"
|
||
import { MyCustom } from "./models/my-custom"
|
||
|
||
const moduleName = "hello"
|
||
const pathToMigrations = __dirname + "/migrations"
|
||
|
||
const migrationScriptOptions = {
|
||
moduleName,
|
||
models: {
|
||
MyCustom,
|
||
},
|
||
pathToMigrations,
|
||
}
|
||
|
||
export const runMigrations = ModulesSdkUtils
|
||
.buildMigrationScript(
|
||
migrationScriptOptions
|
||
)
|
||
|
||
export const revertMigration = ModulesSdkUtils
|
||
.buildRevertMigrationScript(
|
||
migrationScriptOptions
|
||
)
|
||
|
||
const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
|
||
moduleModels: {
|
||
MyCustom,
|
||
},
|
||
moduleRepositories: {
|
||
BaseRepository: MikroOrmBaseRepository,
|
||
},
|
||
moduleServices: [HelloModuleService],
|
||
})
|
||
|
||
const connectionLoader = ModulesSdkUtils.mikroOrmConnectionLoaderFactory({
|
||
moduleName,
|
||
moduleModels: [MyCustom],
|
||
migrationsPath: pathToMigrations,
|
||
})
|
||
|
||
export default {
|
||
service: HelloModuleService,
|
||
runMigrations,
|
||
revertMigration,
|
||
loaders: [containerLoader, connectionLoader],
|
||
}
|
||
```
|
||
|
||
<Note>
|
||
|
||
If you don't have the `MyCustom` data model, refer to [this chapter](../../../basics/data-models/page.mdx).
|
||
|
||
</Note>
|
||
|
||
Also, make sure that the module is added in the `modules` object defined in `medusa-config.js`:
|
||
|
||
```js title="medusa-config.js"
|
||
const modules = {
|
||
helloModuleService: {
|
||
resolve: "./dist/modules/hello",
|
||
},
|
||
// ...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Add List and Create API Routes
|
||
|
||
Create the file `src/api/store/custom/route.ts` with the following content:
|
||
|
||
```ts title="src/api/store/custom/route.ts"
|
||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||
import HelloModuleService from "../../../modules/hello/service"
|
||
import { CreateMyCustomDTO } from "../../../types/hello/my-custom"
|
||
|
||
export async function GET(
|
||
req: MedusaRequest,
|
||
res: MedusaResponse
|
||
): Promise<void> {
|
||
const helloModuleService: HelloModuleService =
|
||
req.scope.resolve(
|
||
"helloModuleService"
|
||
)
|
||
|
||
res.json({
|
||
my_customs: await helloModuleService.list(),
|
||
})
|
||
}
|
||
|
||
type CustomReq = CreateMyCustomDTO
|
||
|
||
export async function POST(
|
||
req: MedusaRequest<CustomReq>,
|
||
res: MedusaResponse
|
||
): Promise<void> {
|
||
const helloModuleService: HelloModuleService =
|
||
req.scope.resolve(
|
||
"helloModuleService"
|
||
)
|
||
|
||
// skipping validation for simplicity
|
||
const myCustom = await helloModuleService.create(req.body)
|
||
|
||
res.json({
|
||
my_custom: myCustom,
|
||
})
|
||
}
|
||
|
||
```
|
||
|
||
This adds two API routes:
|
||
|
||
1. A `GET` API route at `/store/custom` that retrieves a list of `MyCustom` records.
|
||
2. A `POST` API route at `/store/custom` that creates a `MyCustom` record.
|
||
|
||
---
|
||
|
||
## 5. Add Retrieve, Update, and Delete API Routes
|
||
|
||
Create the file `src/api/store/custom/[id]/route.ts` with the following content:
|
||
|
||
```ts title="src/api/store/custom/[id]/route.ts"
|
||
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||
import HelloModuleService from "../../../../modules/hello/service"
|
||
|
||
export async function GET(
|
||
req: MedusaRequest,
|
||
res: MedusaResponse
|
||
): Promise<void> {
|
||
const helloModuleService: HelloModuleService =
|
||
req.scope.resolve(
|
||
"helloModuleService"
|
||
)
|
||
|
||
const myCustom = await helloModuleService.retrieve(req.params.id)
|
||
|
||
res.json({
|
||
my_custom: myCustom,
|
||
})
|
||
}
|
||
|
||
export async function POST(
|
||
req: MedusaRequest,
|
||
res: MedusaResponse
|
||
): Promise<void> {
|
||
const helloModuleService: HelloModuleService =
|
||
req.scope.resolve(
|
||
"helloModuleService"
|
||
)
|
||
|
||
// skipping validation for simplicity
|
||
const myCustom = await helloModuleService.update(
|
||
req.params.id,
|
||
req.body
|
||
)
|
||
|
||
res.json({
|
||
my_custom: myCustom,
|
||
})
|
||
}
|
||
|
||
export async function DELETE(
|
||
req: MedusaRequest,
|
||
res: MedusaResponse
|
||
): Promise<void> {
|
||
const helloModuleService: HelloModuleService =
|
||
req.scope.resolve(
|
||
"helloModuleService"
|
||
)
|
||
|
||
await helloModuleService.delete(req.params.id)
|
||
|
||
res.status(200).json({
|
||
success: true,
|
||
})
|
||
}
|
||
```
|
||
|
||
This creates three API routes:
|
||
|
||
1. A `GET` API route at `/store/custom/[id]` that retrieves a `MyCustom` record by its ID.
|
||
2. A `POST` API route at `/store/custom/[id]` that updates a `MyCustom` record.
|
||
3. A `DELETE` API route at `/store/custom/[id]` that deletes a `MyCustom` record.
|
||
|
||
---
|
||
|
||
## 6. Test it Out
|
||
|
||
Start your Medusa application:
|
||
|
||
```bash npm2yarn
|
||
npm run dev
|
||
```
|
||
|
||
Then, test out each of the API routes you created.
|
||
|
||
<Tabs defaultValue="list-route" layoutType="vertical">
|
||
<TabsList>
|
||
<TabsTrigger value="list-route">
|
||
|
||
<Badge variant="green">GET</Badge> /store/custom
|
||
|
||
</TabsTrigger>
|
||
<TabsTrigger value="create-route">
|
||
|
||
<Badge variant="blue">POST</Badge> /store/custom
|
||
|
||
</TabsTrigger>
|
||
<TabsTrigger value="retrieve-route">
|
||
|
||
<Badge variant="green">GET</Badge> /store/custom/[id]
|
||
|
||
</TabsTrigger>
|
||
<TabsTrigger value="update-route">
|
||
|
||
<Badge variant="blue">POST</Badge> /store/custom/[id]
|
||
|
||
</TabsTrigger>
|
||
<TabsTrigger value="delete-route">
|
||
|
||
<Badge variant="red">DEL</Badge> /store/custom/[id]
|
||
|
||
</TabsTrigger>
|
||
</TabsList>
|
||
<TabsContentWrapper>
|
||
<TabsContent value="list-route">
|
||
|
||
```bash apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/custom"
|
||
curl http://localhost:9000/store/custom
|
||
```
|
||
|
||
</TabsContent>
|
||
<TabsContent value="create-route">
|
||
|
||
```bash apiTesting testApiMethod="POST" testApiUrl="http://localhost:9000/store/custom" testBodyParams={{ "name": "test" }}
|
||
curl -X POST http://localhost:9000/store/custom \
|
||
--header 'Content-Type: application/json' \
|
||
--data-raw '{
|
||
"name": "test"
|
||
}'
|
||
```
|
||
|
||
</TabsContent>
|
||
<TabsContent value="retrieve-route">
|
||
|
||
```bash apiTesting testApiMethod="GET" testApiUrl="http://localhost:9000/store/custom/{id}" testPathParams={{ "id": "mc_123" }}
|
||
curl http://localhost:9000/store/custom/mc_123
|
||
```
|
||
|
||
</TabsContent>
|
||
<TabsContent value="update-route">
|
||
|
||
```bash apiTesting testApiMethod="POST" testApiUrl="http://localhost:9000/store/custom/{id}" testPathParams={{ "id": "mc_123" }} testBodyParams={{ "name": "test" }}
|
||
curl -X POST http://localhost:9000/store/custom/mc_123 \
|
||
--header 'Content-Type: application/json' \
|
||
--data-raw '{
|
||
"name": "test"
|
||
}'
|
||
```
|
||
|
||
</TabsContent>
|
||
<TabsContent value="delete-route">
|
||
|
||
```bash apiTesting testApiMethod="DELETE" testApiUrl="http://localhost:9000/store/custom/{id}" testPathParams={{ "id": "mc_123" }}
|
||
curl -X DELETE http://localhost:9000/store/custom/mc_123
|
||
```
|
||
|
||
</TabsContent>
|
||
</TabsContentWrapper>
|
||
</Tabs>
|