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.

Why are modules isolated?Medusa isolates modules to ensure that they're re-usable across applications, aren't tightly coupled to other resources, and don't have implications when integrated into the Medusa application. Learn more about why modules are isolated in this chapter , and check out this reference for the list of resources in the module's container .

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:

TipLearn how to create a module in this chapter .
src/modules/hello/loaders/hello-world.ts
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.

TipFind the list of resources in the module's container in this reference .

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:

src/modules/hello/index.ts
1// other imports...2import helloWorldLoader from "./loaders/hello-world"3
4export default Module("hello", {5  // ...6  loaders: [helloWorldLoader],7})

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:

Code
info:   [HELLO MODULE] Just started the Medusa application!

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:

src/modules/mongo/loaders/connection.ts
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:

medusa-config.ts
1module.exports = defineConfig({2  // ...3  modules: [4    {5      resolve: "./src/modules/mongo",6      options: {7        connection_url: process.env.MONGO_CONNECTION_URL,8        db_name: process.env.MONGO_DB_NAME,9      },10    },11  ],12})

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:

  1. The key to register the resource under, which in this case is mongoClient. You'll use this name later to resolve the client.
  2. 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:

src/modules/mongo/service.ts
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:

src/modules/mongo/index.ts
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:

Terminal
info:    Connected to MongoDB

You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database.

Was this chapter helpful?
Edit this page