- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
2.7. Loaders
In this chapter, you’ll learn about loaders and how to use them.
What is a Loader?#
When building a commerce application, you'll often need to execute an action the first time the application starts. For example, if you're integrating a non-supported database such as MongoDB, you want to establish the connection when the application starts and re-use it in your customizations.
In Medusa, you can execute an action when the application starts using a loader. A loader is a function exported by a module, which is a package of business logic for a single domain. When the Medusa application starts, it executes all loaders exported by configured modules.
Loaders are useful to register custom resources, such as database connections, in the module's container, which is similar to the Medusa container but includes only resources available to the module. Modules are isolated, so they can't access resources outside of them, such as a service in another module.
How to Create a Loader?#
1. Implement Loader Function#
You create a loader function in a TypeScript or JavaScript file under a module's loaders
directory.
For example, consider you have a hello
module, you can create a loader at src/modules/hello/loaders/hello-world.ts
with the following content:
1import {2 LoaderOptions,3} from "@medusajs/framework/types"4import { 5 ContainerRegistrationKeys,6} from "@medusajs/framework/utils"7 8export default async function helloWorldLoader({9 container,10}: LoaderOptions) {11 const logger = container.resolve(ContainerRegistrationKeys.LOGGER)12 13 logger.info("[helloWorldLoader]: Hello, World!")14}
The loader file exports an async function, which is the function executed when the application loads.
The function receives an object parameter that has a container
property, which is the module's container that you can use to resolve resources from. In this example, you resolve the Logger utility to log a message in the terminal.
2. Export Loader in Module Definition#
After implementing the loader, you must export it in the module's definition in the index.ts
file at the root of the module's directory. Otherwise, the Medusa application will not run it.
So, to export the loader you implemented above in the hello
module, add the following to src/modules/hello/index.ts
:
The second parameter of the Module
function accepts a loaders
property whose value is an array of loader functions. The Medusa application will execute these functions when it starts.
Test the Loader#
Assuming your module is added to Medusa's configuration, you can test the loader by starting the Medusa application:
Then, you'll find the following message logged in the terminal:
This indicates that the loader in the hello
module ran and logged this message.
Example: Register Custom MongoDB Connection#
As mentioned in this chapter's introduction, loaders are most useful when you need to register a custom resource in the module's container to re-use it in other customizations in the module.
Consider your have a MongoDB module that allows you to perform operations on a MongoDB database.
To connect to the database, you create the following loader in your module:
1import { LoaderOptions } from "@medusajs/framework/types"2import { ContainerRegistrationKeys } from "@medusajs/framework/utils"3import { asValue } from "awilix"4import { MongoClient } from "mongodb"5 6type ModuleOptions = {7 connection_url?: string8 db_name?: string9}10 11export default async function mongoConnectionLoader({12 container,13 options,14}: LoaderOptions<ModuleOptions>) {15 if (!options.connection_url) {16 throw new Error(`[MONGO MDOULE]: connection_url option is required.`)17 }18 if (!options.db_name) {19 throw new Error(`[MONGO MDOULE]: db_name option is required.`)20 }21 const logger = container.resolve(ContainerRegistrationKeys.LOGGER)22 23 try {24 const clientDb = (25 await (new MongoClient(options.connection_url)).connect()26 ).db(options.db_name)27 28 logger.info("Connected to MongoDB")29 30 container.register(31 "mongoClient",32 asValue(clientDb)33 )34 } catch (e) {35 logger.error(36 `[MONGO MDOULE]: An error occurred while connecting to MongoDB: ${e}`37 )38 }39}
The loader function accepts in its object parameter an options
property, which is the options passed to the module in Medusa's configurations. For example:
Passing options is useful when your module needs informations like connection URLs or API keys, as it ensures your module can be re-usable across applications. For the MongoDB Module, you expect two options:
connection_url
: the URL to connect to the MongoDB database.db_name
: The name of the database to connect to.
In the loader, you check first that these options are set before proceeding. Then, you create an instance of the MongoDB client and connect to the database specified in the options.
After creating the client, you register it in the module's container using the container's register
method. The method accepts two parameters:
- The key to register the resource under, which in this case is
mongoClient
. You'll use this name later to resolve the client. - The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an
asValue
function imported from the awilix package, which is the package used to implement the container functionality in Medusa.
Use Custom Registered Resource in Module's Service#
After registering the custom MongoDB client in the module's container, you can now resolve and use it in the module's service.
For example:
1import type { Db } from "mongodb"2 3type InjectedDependencies = {4 mongoClient: Db5}6 7export default class MongoModuleService {8 private mongoClient_: Db9 10 constructor({ mongoClient }: InjectedDependencies) {11 this.mongoClient_ = mongoClient12 }13 14 async createMovie({ title }: {15 title: string16 }) {17 const moviesCol = this.mongoClient_.collection("movie")18 19 const insertedMovie = await moviesCol.insertOne({20 title,21 })22 23 const movie = await moviesCol.findOne({24 _id: insertedMovie.insertedId,25 })26 27 return movie28 }29 30 async deleteMovie(id: string) {31 const moviesCol = this.mongoClient_.collection("movie")32 33 await moviesCol.deleteOne({34 _id: {35 equals: id,36 },37 })38 }39}
The service MongoModuleService
resolves the mongoClient
resource you registered in the loader and sets it as a class property. You then use it in the createMovie
and deleteMovie
methods, which create and delete a document in a movie
collection in the MongoDB database, respectively.
Make sure to export the loader in the module's definition in the index.ts
file at the root directory of the module:
1import { Module } from "@medusajs/framework/utils"2import MongoModuleService from "./service"3import mongoConnectionLoader from "./loaders/connection"4 5export const MONGO_MODULE = "mongo"6 7export default Module(MONGO_MODULE, {8 service: MongoModuleService,9 loaders: [mongoConnectionLoader],10})
Test it Out#
You can test the connection out by starting the Medusa application. If it's successful, you'll see the following message logged in the terminal:
You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database.