Get started with Medusa Part 2: Make the Server Your Own
Tags: javascript, tutorial, opensource, webdev
Published: 2021-11-08 15:41:34

In the first part of this tutorial series, I compared [Medusa](https://github.com/medusajs/medusa) and Shopify to showcase how [Medusa](https://dev.to/medusajs/get-started-with-medusa-the-open-source-alternative-to-shopify-305j) [is the open-source alternative to Shopify](https://dev.to/medusajs/get-started-with-medusa-the-open-source-alternative-to-shopify-305j). Where Shopify lacks when it comes to its pricing plans, minimal customization abilities, and inability to fit for every business use case, Medusa can compensate for it.

Medusa is an open-source headless commerce solution that allows you to own your stack and make it fit into whatever use case your business needs. It is fast and very flexible.

In the previous tutorial, you learned about Medusa’s 3 components and how you can install and run each of them. It is a very easy process that can get your store up and running in seconds.

In this tutorial, you will start making changes to the server to make it your own. You will learn how to create new API endpoints, services, and subscribers. The API you will create will retrieve the products with the most sales, and you will create a service and subscriber to help us do that.

The code for this tutorial is on [this GitHub repository](https://github.com/shahednasser/medusa-store-tutorial).


## Prerequisites 

This tutorial assumes you have already read and followed along with [part 1](https://dev.to/medusajs/get-started-with-medusa-the-open-source-alternative-to-shopify-305j). In the first part, you learn how to setup the Medusa store, which you will make changes to in this tutorial, as well as the Medusa storefront and the  admin. If you have not went through it yet, please do before continuing with this tutorial.

In addition, you need to have Redis installed and running on your machine to be able to use subscribers. So, if you do not have it installed and you want to follow along with the tutorial you should go ahead and [install it](http://redis.io).


## Add a Service

As mentioned earlier, you will be creating an API endpoint that allows you to get the top products, i.e. the products with the most sales.

In Medusa, services generally handle the logic of models or entities in one place. They hold helper functions that allow you to retrieve or perform action on these models. Once you put them in a service, you can access the service from anywhere in your Medusa project.

So, in this tutorial, you will create a service `TopProductsService` that will hold all the logic needed to update products with their number of sales and to retrieve the products sorted by their number of sales.

To create a service, start by creating the file `src/services/top-products.js` with the following content:


    import { BaseService } from "Medusa-interfaces";
    
    class TopProductsService extends BaseService {
      constructor({ productService, orderService }) {
        super();
        this.productService_ = productService;
        this.orderService_ = orderService;
      }
    }

Here are a few things to note about this service:


1. When this service is retrieved in other places in your code, the service should be referred to as the camel-case version of the file name followed by “Service”. In this case, the file name is `top-product`, so to access it in other places we use `topProductsService`.
2. Similarly to how you will use this service, we inject as dependencies the `productService` and `orderService` in the constructor. When you create classes in Medusa, you can use dependency injection to get access to services.

**Implement getTopProducts**

The next step is to add the method `getTopProducts` to the `TopProductsService` class. This method will retrieve the products from the database, sort them by their number of sales, then return the top 5 products.

Inside `TopProductsService` class add the new method:


    async getTopProducts() {
      const products = await this.productService_.list({
        status: ['published']
      }, {
        relations: ["variants", "variants.prices", "options", "options.values", "images", "tags", "collection", "type"]
      });
      products.sort((a, b) => {
        const aSales = a.metadata && a.metadata.sales ? a.metadata.sales : 0;
        const bSales = b.metadata && b.metadata.sales ? b.metadata.sales : 0;
        return aSales > bSales ? -1 : (aSales < bSales ? 1 : 0);
      });
      return products.slice(0, 4);
    }

You first use `this.productService_` to retrieve the list of products. Notice that the `list` method can take 2 optional parameters. The first one specifies where conditions, and the second parameter specifies the relations on this products to retrieve.

Then, you sort the array with the [sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) Array method giving it a compare function. In the compare function, you compare the number of sales stored inside the `metadata` field. In Medusa, most entities have the `metadata` field which allows you to easily add custom attributes in the default entities for your purposes. Here, you use the `metadata` field to store the number of sales. You are also sorting the products descending.

Finally, you use the [splice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) Array method to retrieve only the first 5 items.

**Implement updateSales**

Next, you will implement the `updateSales` method in the `TopProductsService`. This method receives an order ID as a parameter, then retrieves this order and loops over the items ordered. Then, the `sales` property inside `metadata` is incremented and the product is updated.

Add the new method in `TopProductsService`:


    async updateSales(orderId) {
      const order = await this.orderService_.retrieve(orderId, {
        relations: ["items", "items.variant", "items.variant.product"]
      });
      if (order.items && order.items.length) {
        for (let i = 0; i < order.items.length; i++) {
          const item = order.items[i];
          //retrieve product by id
          const product = await this.productService_.retrieve(item.variant.product.id, {
            relations: ["variants", "variants.prices", "options", "options.values", "images", "tags", "collection", "type"]
          });
          const sales = product.metadata && product.metadata.sales ? product.metadata.sales : 0;
          //update product
          await this.productService_.update(product.id, {
            metadata: { sales: sales + 1 }
          });
          
        }
      }
    }

You first use `this.orderService_` to retrieve the order by its ID. The `retrieve` method takes the order ID as the first parameter and a config object as the second parameter which is similar to the ones you used in the previous method. You pass it the relations array to retrieve the ordered items and their products.

Then, you loop over the items and use the product id inside each item to retrieve the product. Afterward, you increment the number of sales and update the product using the `update` method on `this.productService_`.

This service is now ready to update product sales numbers and retrieve products ordered based on their sales number.


## Add an API Endpoint

Now, you will add an API endpoint to retrieve the top products. To add an API endpoint, you can do that by creating the file `src/api/index.js` with the following content:


    import { Router } from "express"
    export default () => {
      const router = Router()
      router.get("/store/top-products", async (req, res) => {
        const topProductsService = req.scope.resolve("topProductsService")
        res.json({
          products: await topProductsService.getTopProducts()
        })
      })
      return router;
    }

Creating an endpoint is easy. You just need to export an [Express](https://expressjs.com) Router. This router can hold as many routes as you want.

In this code, you add a new GET route at the endpoint `/store/top-products`. The reason you are using `store` here as a prefix to `top-products` is that Medusa prefixes all storefront endpoints with `/store`, and all admin endpoints with `/admin`. You do not need to add this prefix, but it is good to follow the conventions of the Medusa APIs.

In this route, you retrieve the service you created in the previous section with this line:


    const topProductsService = req.scope.resolve("topProductsService")

You can retrieve any service inside routes using `req.scope.resolve`. As explained in the services section, you need to use the camel-case version of the file name followed by `Service` when referencing a service in your code.

After retrieving the service, you can then use the methods you created on it. So, you return a JSON response that has the key `products` and the value will be the array of top products returned by `getTopProducts`.

Let us test it out. You can access this endpoint at `localhost:9000/store/top-products`. As this is a GET request, you can do it from your browser or using a client like [Postman](https://www.postman.com) or [Thunder Client](https://www.thunderclient.io).

You should see an array of products in the response. At the moment, nothing is sorted as you have not implemented the subscriber which will update the sales number.


![](https://paper-attachments.dropbox.com/s_0E2AAA019E9ECAD398F74CB5188E044A697A4B7712D8B64DE0483D9EA29C0E07_1636115815267_Screen+Shot+2021-11-04+at+7.32.44+PM.png)



## Add a Subscriber

Finally, you will add a subscriber which will update the sales number of products when an order is placed.

Before creating the subscriber, you need to make sure that Redis is installed and running on your machine. You can test that by running the following command in your terminal:      


    redis-cli ping

If the command returns “PONG” then the Redis service is running.

Then, go to `Medusa``-config.js` in the root of your project. You will see that at the end of the file inside the exported config there is this line commented out:


    // redis_url: REDIS_URL,

Remove the comments. This uses the variable `REDIS_URL` declared in the beginning of the file. Its value is either the Redis URL set in `.env` or the default Redis URL `redis://localhost:6379`. If you have a different Redis URL, add the new variable `REDIS_URL` in `.env` with the URL.

Then, restart the server. This will take the updated configuration and connect to your Redis server.

Now, you will implement the subscriber. Create the file `src/subscribers/top-products.js` with the following content:


    class TopProductsSubscriber {
      constructor({ topProductsService, eventBusService }) {
        this.topProductsService_ = topProductsService;
        eventBusService.subscribe("order.placed", this.handleTopProducts);
      }
      handleTopProducts = async (data) => {
        this.topProductsService_.updateSales(data.id);
      };
    }
    export default TopProductsSubscriber;

Similar to how you implemented `TopProductsService`, you pass the `topProductsService` in the constructor using dependency injection. You also pass `eventBusService`. This is used to subscribe a handler to an event in the constructor.

You subscribe to the order placed event with this line:

    eventBusService.subscribe("order.placed", this.handleTopProducts);

The `subscribe` method on `eventBusService` takes the name of the event as the first parameter and the handler as the second parameter.

You then define in the class the `handleTopProducts` method which will handle the `order.placed` event. Event handlers in Medusa generally receive a `data` object that holds an `id` property with the ID of the entity this event is related to. So, you pass this ID into the `updateSales` method on `this.topProductsService_` to update the number of sales for each of the products in the order.


## Test It Out

You will now test everything out. Make sure the server is running. If not, run it with the following command:


    npm start

Then, go to the Medusa storefront installation and run:

    npm run dev

Go to the storefront and place an order. This will trigger the `TopProductsSubscriber` which will update the sales of the products in that order.

Now, send a request to `/store/top-products` like you did before. You should see that `sales` inside the `metadata` property of the products in that order has increased.


![](https://paper-attachments.dropbox.com/s_0E2AAA019E9ECAD398F74CB5188E044A697A4B7712D8B64DE0483D9EA29C0E07_1636115827468_Screen+Shot+2021-11-05+at+1.00.44+PM.png)


Try to add a new product from the admin panel or use the database in [the GitHub repository](https://github.com/shahednasser/medusa-store-tutorial) of this tutorial, which has an additional product. Then, try to make more orders with that product. You will see that the sorting in the endpoint has changed based on the number of sales.


## Conclusion

In this tutorial, you learned how to add custom API endpoint, service, and subscriber. You can use these 3 to implement any custom feature or integration into your store.

In the next tutorial, you will use the API endpoint you created in this part to customize the frontend and add a product slider that showcases the top selling products on your store.

In the meantime, should you have any issues or questions related to Medusa, then feel free to reach out to the Medusa team via [Discord](https://discord.gg/F87eGuwkTp).