diff --git a/www/apps/book/app/learn/basics/loaders/page.mdx b/www/apps/book/app/learn/basics/loaders/page.mdx
index a0f525e0d6ef8..fc378ac359fa5 100644
--- a/www/apps/book/app/learn/basics/loaders/page.mdx
+++ b/www/apps/book/app/learn/basics/loaders/page.mdx
@@ -1,3 +1,5 @@
+import { Prerequisites } from "docs-ui"
+
export const metadata = {
title: `${pageNumber} Loaders`,
}
@@ -8,29 +10,66 @@ In this chapter, you’ll learn about loaders and how to use them.
## What is a Loader?
-A loader is a function executed when the Medusa application starts. You define and export it in a module.
+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](../modules/page.mdx), 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](../../advanced-development/modules/container/page.mdx), which is similar to the [Medusa container](../medusa-container/page.mdx) but includes only [resources available to the module](!resources!/medusa-container-resources#module-container-resources). Modules are isolated, so they can't access resources outside of them, such as a service in another module.
+
+
+
+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](../../advanced-development/modules/isolation/page.mdx), and check out [this reference for the list of resources in the module's container](!resources!/medusa-container-resources#module-container-resources).
-Loaders are useful to perform a task at the application start-up, such as to sync data between Medusa and a third-party service.
+
---
## How to Create a Loader?
-A loader is created in a TypeScript or JavaScript file under a module's `loaders` directory.
+### 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:
-For example, create the file `src/modules/hello/loaders/hello-world.ts` with the following content:
+
+
+Learn how to create a module in [this chapter](../modules/page.mdx).
+
+
```ts title="src/modules/hello/loaders/hello-world.ts"
-export default async function helloWorldLoader() {
- console.log(
- "[HELLO MODULE] Just started the Medusa application!"
- )
+import {
+ LoaderOptions,
+} from "@medusajs/framework/types"
+import {
+ ContainerRegistrationKeys,
+} from "@medusajs/framework/utils"
+
+export default async function helloWorldLoader({
+ container,
+}: LoaderOptions) {
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
+
+ logger.info("[helloWorldLoader]: Hello, World!")
}
```
-### Export Loader in Module Definition
+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.
+
+
+
+Find the list of resources in the module's container in [this reference](!resources!/medusa-container-resources#module-container-resources).
+
+
+
+### 2. Export Loader in Module Definition
-Import the loader in `src/modules/hello/index.ts` and export it in the module's 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`:
```ts title="src/modules/hello/index.ts"
// other imports...
@@ -42,37 +81,209 @@ export default Module("hello", {
})
```
-The value of the `loaders` property is an array of loader functions.
-
----
+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
+### Test the Loader
-Start the Medusa application:
+Assuming your module is [added to Medusa's configuration](../modules/page.mdx#4-add-module-to-medusas-configurations), you can test the loader by starting the Medusa application:
```bash npm2yarn
npm run dev
```
-Among the messages logged in the terminal, you’ll see the following message:
+Then, you'll find the following message logged in the terminal:
-```bash
-[HELLO MODULE] Just started the Medusa application!
+```plain
+info: [HELLO MODULE] Just started the Medusa application!
```
+This indicates that the loader in the `hello` module ran and logged this message.
+
---
-## When to Use Loaders
+## 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.
-- You're performing an action at application start-up.
-- You're establishing a one-time connection with an external system.
+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:
-You want to perform an action continuously or at a set time pattern in the application. Use scheduled jobs instead, which is explained in an upcoming chapter.
+export const loaderHighlights = [
+ ["6", "ModuleOptions", "Define a type for expected options."],
+ ["14", "ModuleOptions", "Pass the option type as a type argument to `LoaderOptions`."],
+ ["24", "clientDb", "Create a client instance that connects to the specified database."],
+ ["30", "register", "Register custom resource in the container."],
+ ["31", `"mongoClient"`, "The resource's key in the container."],
+ ["32", "asValue(clientDb)", "The resource to register."]
+]
-
+```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights}
+import { LoaderOptions } from "@medusajs/framework/types"
+import { ContainerRegistrationKeys } from "@medusajs/framework/utils";
+import { asValue } from "awilix";
+import { MongoClient } from "mongodb"
+
+type ModuleOptions = {
+ connection_url?: string
+ db_name?: string
+}
+
+export default async function mongoConnectionLoader({
+ container,
+ options
+}: LoaderOptions) {
+ if (!options.connection_url) {
+ throw new Error(`[MONGO MDOULE]: connection_url option is required.`)
+ }
+ if (!options.db_name) {
+ throw new Error(`[MONGO MDOULE]: db_name option is required.`)
+ }
+ const logger = container.resolve(ContainerRegistrationKeys.LOGGER)
+
+ try {
+ const clientDb = (
+ await (new MongoClient(options.connection_url)).connect()
+ ).db(options.db_name)
+
+ logger.info("Connected to MongoDB")
+
+ container.register(
+ "mongoClient",
+ asValue(clientDb)
+ )
+ } catch (e) {
+ logger.error(
+ `[MONGO MDOULE]: An error occurred while connecting to MongoDB: ${e}`
+ )
+ }
+}
+```
+
+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:
+
+export const optionHighlights = [
+ ["6", "options", "The options to pass to the module."]
+]
+
+```ts title="medusa-config.ts" highlights={optionHighlights}
+module.exports = defineConfig({
+ // ...
+ modules: [
+ {
+ resolve: "./src/modules/mongo",
+ options: {
+ connection_url: process.env.MONGO_CONNECTION_URL,
+ db_name: process.env.MONGO_DB_NAME
+ }
+ }
+ ]
+})
+```
+
+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](https://github.com/jeffijoe/awilix), 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:
+
+export const serviceHighlights = [
+ ["10", "mongoClient", "Resolve the MongoDB client from the container."],
+ ["11", "mongoClient_", "Set the MongoDB client as a class property."],
+ ["14", "createMovie", "Add a method that uses the MongoDB client to create a document."],
+ ["30", "deleteMovie", "Add a method that uses the MongoDB client to delete a document."]
+]
+
+```ts title="src/modules/mongo/service.ts"
+import type { Db } from "mongodb"
+
+type InjectedDependencies = {
+ mongoClient: Db
+}
+
+export default class MongoModuleService {
+ private mongoClient_: Db
+
+ constructor({ mongoClient }: InjectedDependencies) {
+ this.mongoClient_ = mongoClient
+ }
+
+ async createMovie({ title }: {
+ title: string
+ }) {
+ const moviesCol = this.mongoClient_.collection("movie")
+
+ const insertedMovie = await moviesCol.insertOne({
+ title
+ })
+
+ const movie = await moviesCol.findOne({
+ _id: insertedMovie.insertedId
+ })
+
+ return movie
+ }
+
+ async deleteMovie(id: string) {
+ const moviesCol = this.mongoClient_.collection("movie")
+
+ await moviesCol.deleteOne({
+ _id: {
+ equals: id
+ }
+ })
+ }
+}
+```
+
+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:
+
+```ts title="src/modules/mongo/index.ts" highlights={[["9"]]}
+import { Module } from "@medusajs/framework/utils"
+import MongoModuleService from "./service"
+import mongoConnectionLoader from "./loaders/connection"
+
+export const MONGO_MODULE = "mongo"
+
+export default Module(MONGO_MODULE, {
+ service: MongoModuleService,
+ loaders: [mongoConnectionLoader]
+})
+```
+
+### 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:
+
+```bash
+info: Connected to MongoDB
+```
+
+You can now resolve the MongoDB Module's main service in your customizations to perform operations on the MongoDB database.
diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs
index 3f256c41663b4..77f807d7b6563 100644
--- a/www/apps/book/generated/edit-dates.mjs
+++ b/www/apps/book/generated/edit-dates.mjs
@@ -26,7 +26,7 @@ export const generatedEditDates = {
"app/learn/basics/events-and-subscribers/page.mdx": "2024-09-30T08:43:53.131Z",
"app/learn/advanced-development/modules/container/page.mdx": "2024-11-21T08:59:18.707Z",
"app/learn/advanced-development/workflows/execute-another-workflow/page.mdx": "2024-09-30T08:43:53.129Z",
- "app/learn/basics/loaders/page.mdx": "2024-09-03T08:00:45.993Z",
+ "app/learn/basics/loaders/page.mdx": "2024-11-22T10:51:32.931Z",
"app/learn/advanced-development/admin/widgets/page.mdx": "2024-10-07T12:51:09.969Z",
"app/learn/advanced-development/data-models/page.mdx": "2024-09-19T07:26:43.535Z",
"app/learn/advanced-development/modules/remote-link/page.mdx": "2024-09-30T08:43:53.127Z",