Entities | 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 chapter, we'll talk about entities in Medusa to model your data and manage relationships between products, orders, and customers.

Entities

You can find the transcription of the guide below.

Entities are actually a representation of tables in the database. An example would be the order table. I also want to mention that Medusa uses object-relational mapping (ORM) to create an abstraction between the database and the application.

Let's create an example. I'll create a new entity called Supplier and link it to the product. We can do that by creating a new supplier file in the models and creating a new class called Supplier, and also apply the Entity decorator to it.

We also should inherit the BaseEntity class to add ID, created_at, and updated_at default values. We also have an option to inherit from SoftDeletableEntity, to enable self deletion on this table, and not completely remove record from the database. SoftDeletableEntity inherits from BaseEntity, so it also adds ID, created_at, and updated_at columns.

Let's add some fields to the Supplier. It would be name and contact person. The contact person could be nullable, so I'll add nullable: true to the column decorator. Depending on your case, you can add different relationships. In my case, it will be a one-to-many.

Also, I want to create a relationship between product. Here, we get an error because the product does not have a property supplier. So, we should actually extend the product model and add supplier property to it. We can do this by creating a new product model, but keep in mind that the product model already exists in Medusa. So, we should inherit our product using the Medusa Product Model.

Let's import the Product Model from the Medusa.js medusa package. Let's add the Entity decorator and create a new product class. Now, we have a conflict because we have two products in the same file. So, let's change import to Medusa product to resolve the conflict.

As you can see, we have all of Medusa's fields in the Medusa Product class. For one-to-many relationship, we should define a relationship on both sides using TypeORM. We can do this by adding the ManyToOne decorator. We should also add a JoinColumn property to change the pivot column supplier_id to snake case (instead of camel case, which is the default that TypeORM creates).

Now, I can add an index to the supplier_id column to speed up query performance. If we go back to the Supplier file, we still see the supplier error. To resolve it, we can just change the product import to local. We could add a definition TypeScript file using index.d.ts and overwrite the class properties of the Product.

Overwriting is actually preferable because these models are also used in services. If you want to use services and have the right properties in the models, you should overwrite them. Now, we can test it and import product from Medusa package because it would reference the types definition file.

Once we've created an entity, we should create a migration for it. We can do it using the npx TypeORM migration create command. We should also provide the directory where to put this migration. We should create a new table called supplier and alter the table product to add column supplier_id to create the one-to-many relationship between supplier and product.

For ID, I'm using character varying, which I also use for every text column. Also, here I add a primary key constraint on ID. Here I change product table and add new column supplier_id. Let's differentiate these queries and put them in a seprarate queryRunner.query functions. Also, here I add a new constraint FK_supplier that references the supplier table in the product table.

As we have prepared the new migrations, we provide them in up function. We should also provide a revert migration in case we want to revert the migrations, and we can do this by putting the queries in the down body. To reflect the changes in the migrations directory, we should build the backend. And now, we can run the migrations via npx medusa migrations tun command, and here we are getting the migration was successful.

We can also revert the migrations and we can do this by running npx medusa migrations revert. Now, I will run the migrations again. Also, worth to mention, that we could add a prefix to the newly created records in the database. In this example, I am adding supplier prefix to the supplier entity. You can do this by creating a BeforeInsert method and adding BeforeInsert decorator to this method. And also utilizing, we generateEntityId function from the medusajs/medusa package.

Now that we've created a new entity, we should also create a repository. But before doing that, I want to actually explain why we need repositories in the first place. So, repositories provide us with helper generic methods for an entity, like listing, retrieving records from the table, and counting. We can also provide our own methods in repositories, like in my function example, where we can do some fancy stuff with the QueryBuilder and retrieve some records.

When we access a SupplierRepository, we can see generic helper methods like create, softDelete, softRemove, findOne, and other. As we extended the ProductEntity, which is already defined in medusajs, we should also create a ProductRepository and extend it from the Medusa ProductRepository. It would look the same as creating a new repository, except we should use the extend function and assign the methods of the MedusaProductRepository to our new ProductRepository.

Let's import the default MedusaProductRepository and change the import name to MedusaProductRepository. Just like this, we can assign the methods of the MedusaProductRepository to our new ProductRepository. We can also define our own methods in our new repository, and don't forget to export this repository by default.

Now, let's test it and see how it works. So, I will use our new ProductRepository to list products, and also I'll include the supplier relation to retrieve the suppliers in products. We can utilize the dependency container to retrieve the ProductRepository. I'll also add a generic parameter to have typing right, and also make sure you add typeof because ProductRepository is not a type.

Next, let's use the find method in ProductRepository and add the supplier relation.

Now, let's start the backend. If we go to Postman and hit the endpoint, we'll get the products with the suppliers, which are currently null.

Let's update our custom endpoint and insert a newly created supplier into the product. In this case, we can utilize the ProductRepository.update method and pass the supplier_id as a string. But we don't have a supplier_id, so in order to retrieve it we should create a new supplier. So, to create a new record in the database we can use the SupplierRepository and let's just get it from the dependency container, using SupplierRepository string. Let's also pass typeof SupplierRepository as a generic to have the typing right of this particular SupplierRepository.

Now, let's create a new instance of Supplier using the SupplierRepository.create method. The create function doesn't persist the instance to the database but simply returns a new object of the entity type. In our case, it also adds an ID property to the instance, as we defined in the beforeInsert function in the entity.

We can store the supplier record in the database using the save method, and also let's update the product with the new supplier_id. Also let's list the updated version of the products. We also add the supplier relation to it to get the suppliers. If we send a request to the store custom endpoint, we'll get a new supplier instance with the name and contact person we defined.

Let's switch to the native Medusa endpoint store/products and try to see the suppliers. As you can see, we do not have a supplier property in our products. Medusa also provide the expand parameter, which specifies related entities to include in the response, and aldo Medusa provides fields parameter, that specify which fields of the entity includes in the response. Let's just pass supplier as a relation and id as a field to return and as we can see we have request field supplier is not valid. That's because supplier hasn't been added to the allowed relations for this endpoint.

If we would go to the listProducts endpoint in the Medusa repository, and check the endpoint, we'll see that it has an allowedRelations property attached to it. Besides allowedRelations, we also have defaultRelations, defaultFields, and allowedFields. If we don't pass the fields as a parameter into the endpoint, it uses the defaultFields property, and the same goes for the expand parameter. It would not pass it, it would use defaultRelations.

If you would scroll down, you would see that all of that variables defined in the same file. We need to somehow change the allowedRelations to add supplier to the expand parameter in the endpoint. Besides that, I also want to make a supplier as a default relation to not pass the expand parameter anymore. We can do this by creating a new loader in Medusa. Let's create a new loader called extendStoreProductFields and export as the default. A new function, let's just call the same extendStoreProductFields.

Let's retrieve the storeProducts endpoint file, that I have showed you in a GitHub repository of Medusa and assign it to the property. We can use import function and pass the path of Medusa store product endpoint. Typescript will hint us what properties do we have in that file. And as you can see here, we have allowedStoreProductRelations.

Now, let's rotate it and add supplier string to it. Also, let's do the same with default relations. Let's run yarn dev to spin up server. Now, let's remove fields and just leave store/products. And now, we can see the supplier in the product response.

Let's also test the expand parameter. Let's just add supplier to it. And also, let's add fields with the ID.

And now, we can see the ID and the supplier in the product response.

Let's create new service called supplier. We can do that by creating a new file supplier in the services directory. Let's just copy all of the necessary things from the onboarding service. And also, let's change the injected dependency and add supplierRepositor into it.

Also, make sure you use relative pass instead of absolute to not have any import errors on build.

Here, we just change the naming from the onboarding repository to supplier repository and also change the type.

Let's remove all of the unnecessary methods in our new class. And let's create a new custom method to list and count suppliers. The new method would have a list and count name and as a first parameter of this method, it would be an object whose keys are the attributes of the supplier. In our case, it would be ID, name, contact person, created at and updated at attributes.

Let's utilize selector type from medusajs medusa package and as a genetic parameter, pass the entity. Also, here I forgot to change the service name, so let's fix it. At the second parameter of this method would be the config that is related to the pagination, like skip and take. Also, let's utilize fine config type from medusajs medusa package.

Let's add the supplier repository to the active manager and also assign it to the supplier rep variable. And also, let's create a query and utilize buildQuery helper from medusajs medusa, in order to merge the selector and config and create a query to insert in the find and count methods of the supplier repository.

Now, let's test it in the story custom endpoint. Let's just comment out the logic that we have had previously. And now, let's assign a supplier service from the dependency container and also let's pass the generic. Do not forget about the absolute pass.

Now, let's call our new created method called list and count. And as you can see, as a first parameter, it takes the selector attributes like ID, name, contact person that we can filter by. As a second parameter, we have object that has pagination related attributes like skip and take. Also, we have an order attribute, also relations and also the select that is representation of fields.

Let's change products variable to the suppliers and also let's make list and count method parameters optional. We can just add an object as default. Now, we can test the response by hitting story custom endpoint.

In short

You’ve now learned how to create custom entities in Medusa, link them with existing models, and handle them using repositories. By following this tutorial, you have successfully created a Supplier entity, established a relationship with Product, and tested your setup using Medusa’s powerful migration tools and API features.

In upcoming chapters, we’ll design and implement custom API endpoints to extend Medusa's capabilities and integrate with external services.

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