Dependency Container & Injection | Medusa Development Course

By Viktor Holik

Featured image

Welcome to our Medusa Development Course! In this series, you'll learn how to get your development environment ready and take your first steps in building a Medusa-powered e-commerce application. We'll walk you through everything step by step, helping you master Medusa’s modular architecture and build the key features you need.

This course was made by Viktor Holik, our Software Engineer and Medusa Expert.

In this episode, we’ll be discussing an essential part of Medusa’s architecture: dependency injection and the dependency container. These tools allow Medusa to efficiently manage and provide various services, making it easier to handle complex operations like database transactions, API requests, and service communication.

Dependency Container & Injection

You can find the transcription of the guide below.

Let's talk about the dependency container and dependency injection in Medusa.

Generally speaking, dependency injection is a design pattern that involves passing the required dependencies to a class, as you can see on the screen. To implement this, Medusa uses the dependency container. You can imagine the dependency container as a big box that contains all the dependencies that you get at any time you want.

Let's look at the example of onboarding service. Here, in the constructor, we have the onboarding repository, which is the dependency that the onboarding service needs. I have prepared a diagram to explain this. On the left side, we have the Medusa dependency container, which contains the onboarding repository, the manager, and other stuff. On the right, we have the onboarding service. Every time Medusa starts, the container registers a new onboarding service and, as an argument, passes the container.

Now, let's create an endpoint and test the dependency container over there. Instead of creating an object class onboarding service and passing the required arguments to it, we can use the dependency container. In API routes, you can do this by using the req.scope.resolve function. We can also pass the generic to the resolve function to get the typings right.

And now, let's go to the onboarding service and create some custom method. We will return some string - it doesn't really matter. Now, let's go back to the route handler and execute this method and return it to the client.

Now, if we start the backend, go to Postman, and hit that endpoint, we will get the text that we defined in the method.

Let's go further and create a new service. In this example, I will use the TestService. Let's create some method and then execute this method in the OnboardingService.

Some of you may wonder why we use the super keyword in the constructor of the TestService. The TestService actually inherits the TransactionBaseService class, which is used for handling transactions in a database. We should pass the required arguments to the TransactionBaseService via the super keyword.

Let's go on by creating a new custom method. It will be the same, except I will change the return string. Also, I will remove unnecessary imports.

Let's go back to the OnboardingService. I will also add the TestService to inject a dependency type, to have the type in script. In the constructor, we can access the TestService and assign it to the OnboardingService property, just like this. Now, if we execute this TestService custom method, it will return the string that we defined in the TestService.

Cool. Now, if we start the backend again and go to Postman, we will see the new string..

I also want to show you how you can access the dependency container in the subscribers of Medusa. Let's define a new TestHandler. I'll be using the example from the README. Here, you can access the container by container.resolve. Let's just pass the TestService, and now we can use the TestService in order to replace the handler body.

I also want to highlight that we have registry resources that we can access by default, like the config module, which is defined in the medusa-config.js file. We can also access the entity manager via the manager property. In most cases, the manager is used for handling transactions in the database.

Medusa also has a logging system that we can access with a logger property. With the logger, we can log different messages at different levels, like error, failure, success, debug, info, and others. Let's test it. I will also pass the logger as a generic. Now, if I send a request to the custom store endpoint, I would get this message logged in the console.

Also, I want to note, if you want to see every single registry source in Medusa container, you can check out the dependency injection reference in the Medusa documentation.

Now, I want to emphasize how Medusa registers resources under proper names. Let's look at the diagram I have prepared. As you can see here, the registration of resources in the dependency container is file-based. If something is under the name test in the repository, it will be the TestRepository. The same goes to services. If something is under the services/test directory, it will be the TestService using camel case.

Let's see how this works under the hood. I went to the Medusa repository under the loaders directory in the services file. Here, we have a logic how Medusa registers resources under the services directory. Here, Medusa just getting all the files from the services directory, iterates every single one of them, formats the name using the file name, and then throw it in the container. We can also see the same logic behind model registration.

Also, I want to emphasize how dependency container is crucial, as Medusa comes with the essential commerce endpoints like carts, customers, products, and others. You can easily override the logic behind these routes. Imagine you want to change the listing logic behind the list products endpoint, you can find that the endpoint uses the ProductService and the listAndCount method. And so, we can create our own ProductService, and override the listAndCount method, and add our own custom logic.

We will see an example of this in the services chapter, but for now, I just want to clarify how it works under the hood.

In short

You’ve now seen how critical dependency injection and the dependency container are for managing services, handling custom logic, and integrating new components into your project.

In upcoming chapters, we’ll learn how to work with Medusa entities.

We hope you found Viktor's tutorial insightful and helpful.

Learn how to create a customizable commerce solutions

Check out our Medusa Development Course on YouTube

Other blog posts

Admin Development | Medusa Development Course

Welcome to our Medusa Development Course! Build the user interface for managing suppliers and updating product suppliers in Medusa’s admin panel.

Subscribers | Medusa Development Course

Welcome to our Medusa Development Course! Create and customize subscribers to respond to key events, like sending an email when an order is placed.

Tell us about your project

Got a project in mind? Let's make it happen!

placeholder

Grzegorz Tomaka

Co-CEO & Co-founder

LinkedIn icon
placeholder

Jakub Zbaski

Co-CEO & Co-founder

LinkedIn icon