Medusa Container
In this chapter, you'll learn about the Medusa container and how to use it in your customisations.
What is the Medusa Container?#
The Medusa container is a registry of the resources of the MedusaGo application: the services of its modules, the logger, the database connection, Query, the Link service, and more. You resolve a resource from it by its key, wherever your code needs it.
This is how MedusaGo wires your code to the application without global variables: a route, a workflow step, a script, or a scheduled job each receives the container and resolves what it needs from it.
The container is a *container.MedusaContainer, from the container package of framework. It has these methods:
| Method | Description |
| --- | --- |
| Resolve(key) | Returns the resource registered under the key, as an any. It returns nil if there's none. |
| Has(key) | Reports whether a resource is registered under the key. |
| Register(key, value) | Registers a resource under a key. |
| CreateScope() | Creates a child container. It resolves the resources of its parent, and its own registrations aren't visible to the parent. |
Since Resolve returns an any, you convert the resource to its type with a type assertion.
List of Resources Registered in the Medusa Container#
The container holds these resources. The keys are constants of the common.ContainerRegistrationKeys value, from the common package of utils:
| Key | Resource | Type |
| --- | --- | --- |
| ContainerRegistrationKeys.LOGGER (logger) | The logger of the application. | *slog.Logger |
| ContainerRegistrationKeys.QUERY (query) | Query, to retrieve data across modules. | *query.Query |
| ContainerRegistrationKeys.LINK (link) | The Link service. | *modulessdk.Link |
| ContainerRegistrationKeys.REMOTE_LINK (remoteLink) | The same Link service, under its former name. | *modulessdk.Link |
| ContainerRegistrationKeys.CONFIG_MODULE (configModule) | The configurations of the application. | config.ConfigModule |
| ContainerRegistrationKeys.PG_CONNECTION (__pg_connection__) | The connection pool of the database. | *pgxpool.Pool |
| ContainerRegistrationKeys.FEATURE_FLAG_ROUTER (featureFlagRouter) | The router that tells whether a feature flag is enabled. | *featureflags.FlagRouter |
Each module that the application loads registers its service under its key, such as product, cart, or blog. The keys of the Commerce Modules are constants of the modules-sdk package of utils, such as utilsmodulessdk.PRODUCT (product), utilsmodulessdk.CART (cart), utilsmodulessdk.EVENT_BUS (event_bus), and utilsmodulessdk.WORKFLOW_ENGINE (workflows). The key of a module of your own is its name, or the Key of its configuration. A module that's disabled isn't registered.
How to Resolve From the Medusa Container#
The code that runs in the application receives the container in different ways.
API Route#
In an API route, the container is the Scope of the request. Resolve resources from it:
// listPostsRoute resolves resources from the request scope.
func listPostsRoute(req *medusahttp.MedusaRequest, res *medusahttp.MedusaResponse) error {
logger := req.Scope.Resolve(common.ContainerRegistrationKeys.LOGGER).(*slog.Logger)
blogService := req.Scope.Resolve(blog.Name).(*blog.BlogModuleService)
logger.Info("Listing posts")
posts, err := blogService.Internal("Post").List(req.Ctx(), dal.NewRecord(), utilsmodulessdk.FindConfig{})
if err != nil {
return err
}
body := dal.NewRecord()
body.Set("posts", posts)
return res.JSON(body)
}
Workflow Step#
In a step of a workflow, the container is the Container field of the StepExecutionContext that the step function receives:
// listPostsStep resolves resources from the container of the workflow.
var listPostsStep = composer.CreateStep("list-posts-step",
func(ctx composer.StepExecutionContext, _ any) (composer.StepResponse[[]*dal.Record], error) {
blogService := ctx.Container.Resolve(blog.Name).(*blog.BlogModuleService)
posts, err := blogService.Internal("Post").List(ctx.Context, dal.NewRecord(), utilsmodulessdk.FindConfig{})
if err != nil {
return composer.StepResponse[[]*dal.Record]{}, err
}
return composer.NewStepResponse(posts), nil
}, nil)
Custom CLI Script#
A script receives the container in the Container field of its ExecArgs:
// MyScript logs the number of products. It is registered with the name
// "my-script".
func MyScript(ctx context.Context, args typescommon.ExecArgs) error {
logger := args.Container.Resolve(common.ContainerRegistrationKeys.LOGGER).(*slog.Logger)
q := args.Container.Resolve(common.ContainerRegistrationKeys.QUERY).(*query.Query)
res, err := q.Graph(ctx, query.GraphInput{
Entity: "product",
Fields: []string{"id"},
}, joiner.Options{})
if err != nil {
return err
}
logger.Info(fmt.Sprintf("Found %d products", len(res.Data)))
return nil
}
Scheduled Job#
The function of a scheduled job receives the container as its second argument.
Module Services and Loaders#
A module's Service function receives the container in the Container field of its ServiceOptions, as a Resolver, an interface with the Resolve method. Loaders don't receive the container: they receive the database handle and the module's options.
Register Your Own Resources#
To register a resource of your own, call Register on the container when the application is loaded. The OnLoad option of cli.Run takes hooks that run after the application is loaded, in the start, develop, exec, and user commands. Each hook receives the loaded application, with its container:
// Greeter is a resource of the project.
type Greeter struct{ Greeting string }
// registerResources registers the resources of the project in the container
// once the application is loaded. Pass it in the OnLoad option of cli.Run.
func registerResources(_ context.Context, app *loaders.MedusaApp) error {
app.Container.Register("greeter", &Greeter{Greeting: "Hello"})
return nil
}
Pass the hook in the OnLoad option, in main.go:
// runWithResources runs the CLI with the hook that registers the resources.
func runWithResources(args []string) int {
return cli.Run(args, cli.Options{
Config: Config,
OnLoad: []func(ctx context.Context, app *loaders.MedusaApp) error{registerResources},
})
}
Then resolve the resource wherever you have the container:
// greet resolves the resource that registerResources registered.
func greet(c *container.MedusaContainer, name string) string {
greeter := c.Resolve("greeter").(*Greeter)
return greeter.Greeting + ", " + name
}
Check that a resource exists with Has, as Resolve returns nil for a key that isn't registered, and a type assertion on nil panics.