www/apps/book/app/learn/fundamentals/workflows/page.mdx
import { CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: ${pageNumber} Workflows,
}
In this chapter, you’ll learn about workflows and how to define and execute them.
In digital commerce you typically have many systems involved in your operations. For example, you may have an ERP system that holds product master data and accounting reports, a CMS system for content, a CRM system for managing customer campaigns, a payment service to process credit cards, and so on. Sometimes you may even have custom built applications that need to participate in the commerce stack. One of the biggest challenges when operating a stack like this is ensuring consistency in the data spread across systems.
Medusa has a built-in durable execution engine to help complete tasks that span multiple systems. You orchestrate your operations across systems in Medusa instead of having to manage it yourself. Other commerce platforms don't have this capability, which makes them a bottleneck to building customizations and iterating quickly.
A workflow is a series of queries and actions, called steps, that complete a task. You construct a workflow similar to how you create a JavaScript function.
However, unlike regular functions, workflows:
You implement all custom flows within workflows, then execute them from API routes, subscribers, and scheduled jobs.
A workflow is made of a series of steps. A step is created using createStep from the Workflows SDK.
Create the file src/workflows/hello-world.ts with the following content:
export const step1Highlights = [
["4", "step-1", "The step's unique name."],
["6", "Hello from step one!", "The step's returned data."]
]
import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk"
const step1 = createStep(
"step-1",
async () => {
return new StepResponse(`Hello from step one!`)
}
)
The createStep function accepts the step's unique name as a first parameter, and the step's function as a second parameter.
Steps must return an instance of StepResponse, whose parameter is the data to return to the workflow executing the step.
Steps can accept input parameters. For example, add the following to src/workflows/hello-world.ts:
export const step2Highlights = [ ["7", "name", "Recieve step input as a parameter."] ]
type WorkflowInput = {
name: string
}
const step2 = createStep(
"step-2",
async ({ name }: WorkflowInput) => {
return new StepResponse(`Hello ${name} from step two!`)
}
)
This adds another step whose function accepts as a parameter an object with a name property.
Next, add the following to the same file to create the workflow using the createWorkflow function:
export const workflowHighlights = [
["10", "hello-world", "The workflow's unique name."],
["11", "input", "The workflow's input is passed as a parameter."],
["16", "WorkflowResponse", "The workflow returns an instance of WorkflowResponse."]
]
import {
// other imports...
createWorkflow,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
// ...
const myWorkflow = createWorkflow(
"hello-world",
function (input: WorkflowInput) {
const str1 = step1()
// to pass input
const str2 = step2(input)
return new WorkflowResponse({
message: str2,
})
}
)
export default myWorkflow
The createWorkflow function accepts the workflow's unique name as a first parameter, and the workflow's function as a second parameter. The workflow can accept input which is passed as a parameter to the function.
The workflow must return an instance of WorkflowResponse, whose parameter is returned to workflow executors.
You can execute a workflow from different customizations:
To execute the workflow, invoke it passing the Medusa container as a parameter. Then, use its run method:
```ts title="src/api/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
import type {
MedusaRequest,
MedusaResponse,
} from "@medusajs/framework/http"
import myWorkflow from "../../workflows/hello-world"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
const { result } = await myWorkflow(req.scope)
.run({
input: {
name: "John",
},
})
res.send(result)
}
```
```ts title="src/subscribers/order-placed.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
import {
type SubscriberConfig,
type SubscriberArgs,
} from "@medusajs/framework"
import myWorkflow from "../workflows/hello-world"
export default async function handleOrderPlaced({
event: { data },
container,
}: SubscriberArgs<{ id: string }>) {
const { result } = await myWorkflow(container)
.run({
input: {
name: "John",
},
})
console.log(result)
}
export const config: SubscriberConfig = {
event: "order.placed",
}
```
```ts title="src/jobs/message-daily.ts" highlights={[["7"], ["8"], ["9"], ["10"], ["11"], ["12"]]}
import { MedusaContainer } from "@medusajs/framework/types"
import myWorkflow from "../workflows/hello-world"
export default async function myCustomJob(
container: MedusaContainer
) {
const { result } = await myWorkflow(container)
.run({
input: {
name: "John",
},
})
console.log(result.message)
}
export const config = {
name: "run-once-a-day",
schedule: `0 0 * * *`,
};
```
To test out your workflow, start your Medusa application:
npm run dev
Then, if you added the API route above, send a GET request to /workflow:
curl http://localhost:9000/workflow
You’ll receive the following response:
{
"message": "Hello John from step two!"
}
A step receives an object as a second parameter with configurations and context-related properties. One of these properties is the container property, which is the Medusa container to allow you to resolve Framework and commerce tools in your application.
For example, consider you want to implement a workflow that returns the total products in your application. Create the file src/workflows/product-count.ts with the following content:
export const highlights = [
["10", "container", "Receive the Medusa container as a parameter"],
["11", "resolve", "Resolve the Product Module's main service."],
[
"11",
"product",
"The resource registration name.",
],
]
import {
createStep,
StepResponse,
createWorkflow,
WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
const getProductCountStep = createStep(
"get-product-count",
async (_, { container }) => {
const productModuleService = container.resolve("product")
const [, count] = await productModuleService.listAndCountProducts()
return new StepResponse(count)
}
)
const productCountWorkflow = createWorkflow(
"product-count",
function () {
const count = getProductCountStep()
return new WorkflowResponse({
count,
})
}
)
export default productCountWorkflow
In getProductCountStep, you use the container to resolve the Product Module's main service. Then, you use its listAndCountProducts method to retrieve the total count of products and return it in the step's response. You then execute this step in the productCountWorkflow.
You can now execute this workflow in a custom API route, scheduled job, or subscriber to get the total count of products.
<Note title="Tip">Find a full list of the registered resources in the Medusa container and their registration key in this reference. You can use these resources in your custom workflows.
</Note>