Create a Batch Job Strategy
In this document, you’ll learn how to create a batch job strategy on your Medusa server.
If you’re interested to learn more about what Batch Jobs are and how they work, check out this documentation.
Overview
Batch jobs can be used to perform long tasks in the background of your Medusa server. Batch jobs are handled by batch job strategies. An example of a batch job strategy is the Import Products functionality.
This documentation helps you learn how to create a batch job strategy. The batch job strategy used in this example changes the status of all draft products to published
.
Prerequisites
Medusa Components
It is assumed that you already have a Medusa server installed and set up. If not, you can follow our quickstart guide to get started.
Redis
Redis is required for batch jobs to work. Make sure you install Redis and configure it with your Medusa server.
PostgreSQL
If you use SQLite during your development, it’s highly recommended that you use PostgreSQL when working with batch jobs. Learn how to install PostgreSQL and configure it with your Medusa server.
1. Create a File
A batch job strategy is essentially a class defined in a TypeScript or JavaScript file. You should create this file in src/strategies
.
Following the example used in this documentation, create the file src/strategies/publish.ts
.
2. Create Class
Batch job strategies must extend the abstract class AbstractBatchJobStrategy
and implement its abstract methods.
Add the following content to the file you created:
import {
AbstractBatchJobStrategy,
BatchJobService,
} from "@medusajs/medusa"
import { EntityManager } from "typeorm"
class PublishStrategy extends AbstractBatchJobStrategy {
protected batchJobService_: BatchJobService
processJob(batchJobId: string): Promise<void> {
throw new Error("Method not implemented.")
}
buildTemplate(): Promise<string> {
throw new Error("Method not implemented.")
}
protected manager_: EntityManager
protected transactionManager_: EntityManager
}
export default PublishStrategy
3. Define Required Properties
A batch job strategy class must have two static properties: the identifier
and batchType
properties. The identifier
must be a unique string associated with your batch job strategy, and batchType
must be the batch job's type.
You will use the batchType
later when you interact with the Batch Job APIs.
Following the same example, add the following properties to the PublishStrategy
class:
class PublishStrategy extends AbstractBatchJobStrategy {
static identifier = "publish-products-strategy"
static batchType = "publish-products"
// ...
}
4. Define Methods
(Optional) prepareBatchJobForProcessing
Medusa runs this method before it creates the batch job to prepare the content of the batch job record in the database. It accepts two parameters: the batch job data sent in the body of the Create Batch Job request, and the request instance.
Implementing this method is optional. For example:
class PublishStrategy extends AbstractBatchJobStrategy {
// ...
async prepareBatchJobForProcessing(
batchJob: CreateBatchJobInput,
req: Express.Request
): Promise<CreateBatchJobInput> {
// make changes to the batch job's fields...
return batchJob
}
}
(Optional) preProcessBatchJob
Medusa runs this method after it creates the batch job, but before it is confirmed and processed. You can use this method to perform any necessary action before the batch job is processed. You can also use this method to add information related to the expected result.
For example, this implementation of the preProcessBatchJob
method calculates how many draft products it will published and adds it to the result
attribute of the batch job:
class PublishStrategy extends AbstractBatchJobStrategy {
// ...
async preProcessBatchJob(batchJobId: string): Promise<void> {
return await this.atomicPhase_(async (transactionManager) => {
const batchJob = (await this.batchJobService_
.withTransaction(transactionManager)
.retrieve(batchJobId))
const count = await this.productService_
.withTransaction(transactionManager)
.count({
status: ProductStatus.DRAFT,
})
await this.batchJobService_
.withTransaction(transactionManager)
.update(batchJob, {
result: {
advancement_count: 0,
count,
stat_descriptors: [
{
key: "product-publish-count",
name: "Number of products to publish",
message: `${count} product(s) will be published.`,
},
],
},
})
})
}
}
The result
attribute is an object that can hold many properties including:
count
: used to indicate how many items (in this case, products) that the task will run on.advancement_count
: used to indicate the current number of items processed at a given moment. Since the batch job isn't processed yet, you set it to zero.stat_descriptors
: can be used to show human-readable messages.
processJob
Medusa runs this method to process the batch job once it is confirmed.
For example, this implementation of the processJob
method retrieves all draft products and changes their status to published:
class PublishStrategy extends AbstractBatchJobStrategy {
// ...
async processJob(batchJobId: string): Promise<void> {
return await this.atomicPhase_(
async (transactionManager) => {
const productServiceTx = this.productService_
.withTransaction(transactionManager)
const productList = await productServiceTx
.list({
status: [ProductStatus.DRAFT],
})
productList.forEach(async (product: Product) => {
await productServiceTx
.update(product.id, {
status: ProductStatus.PUBLISHED,
})
})
await this.batchJobService_
.withTransaction(transactionManager)
.update(batchJobId, {
result: {
advancement_count: productList.length,
},
})
}
)
}
}
When a batch job is canceled, the processing of the batch job doesn’t automatically stop. You will have to manually check for changes in the status of the batch job. For example, you can retrieve the batch job and use the condition batchJob.status === BatchJobStatus.CANCELED
to check if the batch job was canceled.
buildTemplate
This method can be used in cases where you provide a template file to download, such as when implementing an import or export functionality.
If not necessary to your use case, you can simply return an empty string:
class PublishStrategy extends AbstractBatchJobStrategy {
// ...
async buildTemplate(): Promise<string> {
return ""
}
}
(Optional) shouldRetryOnProcessingError
Medusa uses this method to decide whether it should retry the batch job if an error occurs during processing.
By default, the AbstractBatchJobStrategy
class implements this method and returns false
, indicating that if a batch job’s process fails it will not be retried.
If you would like to change that behavior, you can override this method to return a different value:
class PublishStrategy extends AbstractBatchJobStrategy {
// ...
protected async shouldRetryOnProcessingError(
batchJob: BatchJob,
err: unknown
): Promise<boolean> {
return true
}
}
(Optional) handleProcessingError
Medusa uses this method to handle errors that occur during processing. By default, it changes the status of the batch job to failed
and sets the errors
property of the batch job’s result
attribute.
You can use this method as implemented in AbstractBatchJobStrategy
at any point in your batch job process to set the batch job as failed.
You can also override this method in your batch job strategy and change how it works:
class PublishStrategy extends AbstractBatchJobStrategy {
// ...
protected async handleProcessingError<T>(
batchJobId: string,
err: unknown,
result: T
): Promise<void> {
// different implementation...
}
}
5. Run Build Command
After you create the batch job and before testing it out, you must run the build command in the directory of your Medusa server:
npm run build
Test your Batch Job Strategy
This section covers how to test and use your batch job strategy. Make sure to start your server first:
npm run start
If your start
script uses the medusa develop
command, whenever you make changes in the src
directory the build
command will automatically run and the server will restart.
You must also use an authenticated user to send batch job requests. You can refer to the authentication guide in the API reference for more details.
If you follow along with the JS Client code snippets, make sure to install and set it up first.
Create Batch Job
The first step is to create a batch job using the Create Batch Job endpoint. In the body of the request, you must set the type
to the value of batchType
in the batch job strategy you created.
For example, this creates a batch job of the type publish-products
:
- Medusa JS Client
- Fetch API
- cURL
medusa.admin.batchJobs.create({
type: "publish-products",
context: { },
dry_run: true,
})
.then(( batch_job ) => {
console.log(batch_job.status)
})
fetch(`<YOUR_SERVER>/admin/batch-jobs`, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "publish-products",
context: { },
dry_run: true,
}),
})
.then((response) => response.json())
.then(({ batch_job }) => {
console.log(batch_job.status)
})
curl -L -X POST '<YOUR_SERVER>/admin/batch-jobs' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \
--data-raw '{
"type": "publish-products",
"context": { },
"dry_run": true
}'
You set the dry_run
to true
to disable automatic confirmation and running of the batch job. If you want the batch job to run automatically, you can remove this body parameter.
Make sure to replace <YOUR_SERVER>
with the server URL where applicable.
(Optional) Retrieve Batch Job
You can retrieve the batch job afterward to get its status and view details about the process in the result
property:
- Medusa JS Client
- Fetch API
- cURL
medusa.admin.batchJobs.retrieve(batchJobId)
.then(( batch_job ) => {
console.log(batch_job.status, batch_job.result)
})
fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}`, {
credentials: "include",
})
.then((response) => response.json())
.then(({ batch_job }) => {
console.log(batch_job.status, batch_job.result)
})
curl -L -X GET '<YOUR_SERVER>/admin/batch-jobs/<BATCH_JOB_ID>' \
-H 'Authorization: Bearer <API_TOKEN>'
# <BATCH_JOB_ID> is the ID of the batch job
Based on the batch job strategy implemented in this documentation, the result
property could be something like this:
"result": {
"count": 1,
"stat_descriptors": [
{
"key": "product-publish-count",
"name": "Number of products to publish",
"message": "1 product(s) will be published."
}
],
"advancement_count": 0
},
Confirm Batch Job
To process the batch job, send a request to confirm the batch job:
- Medusa JS Client
- Fetch API
- cURL
medusa.admin.batchJobs.confirm(batchJobId)
.then(( batch_job ) => {
console.log(batch_job.status)
})
fetch(`<YOUR_SERVER>/admin/batch-jobs/${batchJobId}/confirm`, {
method: "POST",
credentials: "include",
})
.then((response) => response.json())
.then(({ batch_job }) => {
console.log(batch_job.status)
})
curl -L -X POST '<YOUR_SERVER>/admin/batch-jobs/<BATCH_JOB_ID>/confirm' \
-H 'Authorization: Bearer <API_TOKEN>'
# <BATCH_JOB_ID> is the ID of the batch job
The batch job will start processing afterward. Based on the batch job strategy implemented in this documentation, draft products will be published.
You can retrieve the batch job at any given point to check its status.