2.4.2. Guide: Sync Brands from Medusa to CMS

In the previous chapter, you created a CMS Module that integrates a dummy third-party system. You can now perform actions using that module within your custom flows.

In another previous chapter, you added a workflow that creates a brand. After integrating the CMS, you want to sync that brand to the third-party system as well.

Medusa has an event system that emits events when an operation is performed. It allows you to listen to those events and perform an asynchronous action in a function called a subscriber. This is useful to perform actions that aren't integral to the original flow, such as syncing data to a third-party system.

NoteLearn more about Medusa's event system and subscribers in this chapter.

In this chapter, you'll modify the createBrandWorkflow you created before to emit a custom event that indicates a brand was created. Then, you'll listen to that event in a subscriber to sync the brand to the third-party CMS. You'll implement the sync logic within a workflow that you execute in the subscriber.

1. Emit Event in createBrandWorkflow#

Since syncing the brand to the third-party system isn't integral to creating a brand, you'll emit a custom event indicating that a brand was created.

Medusa provides an emitEventStep that allows you to emit an event in your workflows. So, in the createBrandWorkflow defined in src/workflows/create-brand.ts, use the emitEventStep helper step after the createBrandStep:

src/workflows/create-brand.ts
1// other imports...2import { 3  emitEventStep,4} from "@medusajs/medusa/core-flows"5
6// ...7
8export const createBrandWorkflow = createWorkflow(9  "create-brand",10  (input: CreateBrandInput) => {11    // ...12
13    emitEventStep({14      eventName: "brand.created",15      data: {16        id: brand.id,17      },18    })19
20    return new WorkflowResponse(brand)21  }22)

The emitEventStep accepts an object parameter having two properties:

  • eventName: The name of the event to emit. You'll use this name later to listen to the event in a subscriber.
  • data: The data payload to emit with the event. This data is passed to subscribers that listen to the event. You add the brand's ID to the data payload, informing the subscribers which brand was created.

You'll learn how to handle this event in a later step.


2. Create Sync to Third-Party System Workflow#

The subscriber that will listen to the brand.created event will sync the created brand to the third-party CMS. So, you'll implement the syncing logic in a workflow, then execute the workflow in the subscriber.

Workflows have a built-in durable execution engine that helps you complete tasks spanning multiple systems. Also, their rollback mechanism ensures that data is consistent across systems even when errors occur during execution.

NoteLearn more about workflows in this chapter.

You'll create a syncBrandToSystemWorkflow that has two steps:

  • useQueryGraphStep: a step that Medusa provides to retrieve data using Query. You'll use this to retrieve the brand's details using its ID.
  • syncBrandToCmsStep: a step that you'll create to sync the brand to the CMS.

syncBrandToCmsStep#

To implement the step that syncs the brand to the CMS, create the file src/workflows/sync-brands-to-cms.ts with the following content:

Directory structure of the Medusa application after adding the file

src/workflows/sync-brands-to-cms.ts
5import CmsModuleService from "../modules/cms/service"6
7type SyncBrandToCmsStepInput = {8  brand: InferTypeOf<typeof Brand>9}10
11const syncBrandToCmsStep = createStep(12  "sync-brand-to-cms",13  async ({ brand }: SyncBrandToCmsStepInput, { container }) => {14    const cmsModuleService: CmsModuleService = container.resolve(CMS_MODULE)15
16    await cmsModuleService.createBrand(brand)17
18    return new StepResponse(null, brand.id)19  },20  async (id, { container }) => {21    if (!id) {22      return23    }24
25    const cmsModuleService: CmsModuleService = container.resolve(CMS_MODULE)26
27    await cmsModuleService.deleteBrand(id)28  }29)

You create the syncBrandToCmsStep that accepts a brand as an input. In the step, you resolve the CMS Module's service from the Medusa container and use its createBrand method. This method will create the brand in the third-party CMS.

You also pass the brand's ID to the step's compensation function. In this function, you delete the brand in the third-party CMS if an error occurs during the workflow's execution.

NoteLearn more about compensation functions in this chapter.

Create Workflow#

You can now create the workflow that uses the above step. Add the workflow to the same src/workflows/sync-brands-to-cms.ts file:

src/workflows/sync-brands-to-cms.ts
1// other imports...2import { 3  // ...4  createWorkflow, 5  WorkflowResponse,6} from "@medusajs/framework/workflows-sdk"7import { useQueryGraphStep } from "@medusajs/medusa/core-flows"8
9// ...10
11type SyncBrandToCmsWorkflowInput = {12  id: string13}14
15export const syncBrandToCmsWorkflow = createWorkflow(16  "sync-brand-to-cms",17  (input: SyncBrandToCmsWorkflowInput) => {18    // @ts-ignore19    const { data: brands } = useQueryGraphStep({20      entity: "brand",21      fields: ["*"],22      filters: {23        id: input.id,24      },25      options: {26        throwIfKeyNotFound: true,27      },28    })29
30    syncBrandToCmsStep({31      brand: brands[0],32    } as SyncBrandToCmsStepInput)33
34    return new WorkflowResponse({})35  }36)

You create a syncBrandToCmsWorkflow that accepts the brand's ID as input. The workflow has the following steps:

  • useQueryGraphStep: Retrieve the brand's details using Query. You pass the brand's ID as a filter, and set the throwIfKeyNotFound option to true so that the step throws an error if a brand with the specified ID doesn't exist.
  • syncBrandToCmsStep: Create the brand in the third-party CMS.

You'll execute this workflow in the subscriber next.

NoteLearn more about useQueryGraphStep in this reference.

3. Handle brand.created Event#

You now have a workflow with the logic to sync a brand to the CMS. You need to execute this workflow whenever the brand.created event is emitted. So, you'll create a subscriber that listens to and handle the event.

Subscribers are created in a TypeScript or JavaScript file under the src/subscribers directory. So, create the file src/subscribers/brand-created.ts with the following content:

Directory structure of the Medusa application after adding the subscriber

src/subscribers/brand-created.ts
1import type {2  SubscriberConfig,3  SubscriberArgs,4} from "@medusajs/framework"5import { syncBrandToCmsWorkflow } from "../workflows/sync-brands-to-cms"6
7export default async function brandCreatedHandler({8  event: { data },9  container,10}: SubscriberArgs<{ id: string }>) {11  await syncBrandToCmsWorkflow(container).run({12    input: data,13  })14}15
16export const config: SubscriberConfig = {17  event: "brand.created",18}

A subscriber file must export:

  • The asynchronous function that's executed when the event is emitted. This must be the file's default export.
  • An object that holds the subscriber's configurations. It has an event property that indicates the name of the event that the subscriber is listening to.

The subscriber function accepts an object parameter that has two properties:

  • event: An object of event details. Its data property holds the event's data payload, which is the brand's ID.
  • container: The Medusa container used to resolve framework and commerce tools.

In the function, you execute the syncBrandToCmsWorkflow, passing it the data payload as an input. So, everytime a brand is created, Medusa will execute this function, which in turn executes the workflow to sync the brand to the CMS.

NoteLearn more about subscribers in this chapter.

Test it Out#

To test the subscriber and workflow out, you'll use the Create Brand API route you created in a previous chapter.

First, start the Medusa application:

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:

Code
1curl -X POST 'http://localhost:9000/auth/user/emailpass' \2-H 'Content-Type: application/json' \3--data-raw '{4    "email": "admin@medusa-test.com",5    "password": "supersecret"6}'

Make sure to replace the email and password with your admin user's credentials.

TipDon't have an admin user? Refer to this guide.

Then, send a POST request to /admin/brands, passing the token received from the previous request in the Authorization header:

Code
1curl -X POST 'http://localhost:9000/admin/brands' \2-H 'Content-Type: application/json' \3-H 'Authorization: Bearer {token}' \4--data '{5    "name": "Acme"6}'

This request returns the created brand. If you check the logs, you'll find the brand.created event was emitted, and that the request to the third-party system was simulated:

Code
1info:    Processing brand.created which has 1 subscribers2http:    POST /admin/brands ← - (200) - 16.418 ms3info:    Sending a POST request to /brands.4info:    Request Data: {5  "id": "01JEDWENYD361P664WRQPMC3J8",6  "name": "Acme",7  "created_at": "2024-12-06T11:42:32.909Z",8  "updated_at": "2024-12-06T11:42:32.909Z",9  "deleted_at": null10}11info:    API Key: "123"

Next Chapter: Sync Brand from Third-Party CMS to Medusa#

You can also automate syncing data from a third-party system to Medusa at a regular interval. In the next chapter, you'll learn how to sync brands from the third-party CMS to Medusa once a day.

Was this chapter helpful?
Edit this page