DEV Community

Cover image for How You Can Create a Custom Endpoint in Medusa
Shahed Nasser for Medusa

Posted on • Originally published at medusajs.com

How You Can Create a Custom Endpoint in Medusa

Medusa is an open source headless commerce platform built using Node.js. It aims to provide developers with a great experience and limitless customization capabilities.

The core of Medusa is the headless server. It exposes a set of APIs that the other 2 components, admin and storefront, can use to connect to the server and access or manipulate data.

In this tutorial, you’ll learn how to create custom endpoints on your Medusa server for both the storefront and admin.

Prerequisites

This tutorial assumes you already have a Medusa server installed. If not, you can follow the Quickstart guide to get started in a few minutes.

Overview

Custom endpoints reside under the src/api directory in your Medusa Backend. To define a new endpoint, you can add the file index.js under the src/api directory. This file should export a function that returns an Express router.

Your endpoint can be under any path you wish.

Storefront Endpoint

By Medusa’s conventions, all Storefront REST APIs are prefixed by /store. For example, the /store/products lets you retrieve the products to display them on your storefront.

Admin Endpoint

By Medusa’s conventions, all Admin REST APIs are prefixed by /admin. For example, the /admin/products lets you retrieve the products to display them on your Admin.

Implementation

To create a new endpoint, start by creating a new file in src/api called index.js. At its basic format, index.js should look something like this:

import { Router } from "express"

export default () => {
  const router = Router()

  router.get("/hello", (req, res) => {
    res.json({
      message: "Welcome to Your Store!",
    })
  })

  return router
}
Enter fullscreen mode Exit fullscreen mode

This endpoint is accessible under the path /hello. If you want to create an endpoint for the storefront and follow Medusa’s conventions you can prefix the path with /store:

router.get("/store/hello", (req, res) => {
Enter fullscreen mode Exit fullscreen mode

Similarly, you can create an endpoint for the admin and follow Medusa’s conventions by prefixing the path with /admin:

router.get("/admin/hello", (req, res) => {
Enter fullscreen mode Exit fullscreen mode

Making Endpoints Accessible from the Admin

If you’re customizing the admin dashboard or creating your own, you need to use the cors library. An OPTIONS request should be added for each route and handle the requests with the cors library.

First, you need to import your Medusa’s configurations along with the cors library:

import cors from "cors"
import { projectConfig } from "../../medusa-config"
Enter fullscreen mode Exit fullscreen mode

Then, create an object that holds the CORS configurations:

const corsOptions = {
  origin: projectConfig.admin_cors.split(","),
  credentials: true,
}
Enter fullscreen mode Exit fullscreen mode

Finally, for each route you add, create an OPTIONS request:

router.options("/admin/hello", cors(corsOptions))
router.get("/admin/hello", (req, res) => {
  //...
})
Enter fullscreen mode Exit fullscreen mode

Multiple Endpoints

Same File

You can add more than one endpoints in src/api/index.js:

router.get("/admin/hello", (req, res) => {
  res.json({
    message: "Welcome to Your Store!",
  })
})

router.get("/admin/bye", (req, res) => {
  res.json({
    message: "Come back again!",
  })
})
Enter fullscreen mode Exit fullscreen mode

Multiple Files

Alternatively, you can add multiple files for each endpoint or set of endpoints for readability and easy maintenance.

To do that with the previous example, first, create the file src/api/hello.js with the following content:

export default (router) => {
  router.get("/admin/hello", (req, res) => {
    res.json({
      message: "Welcome to Your Store!",
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

You export a function that receives an Express router as a parameter and adds the endpoint admin/hello to it.

Next, create the file src/api/bye.js with the following content:

export default (router) => {
  router.get("/admin/bye", (req, res) => {
    res.json({
      message: "Come back again!",
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

Again, you export a function that receives an Express router as a parameter and adds the endpoint admin/bye to it.

Finally, in src/api/index.js import the two functions at the beginning of the file:

import helloRoute from "./hello"
import byeRoute from "./bye"
Enter fullscreen mode Exit fullscreen mode

and in the exported function, call each of the functions passing them the Express router:

export default () => {
  const router = Router()

  helloRoute(router)
  byeRoute(router)

  return router
}
Enter fullscreen mode Exit fullscreen mode

Use Services

Services in Medusa bundle a set of functionalities related to a model into one class. Then, you can use that class anywhere in your backend. For example, you can use the ProductService to retrieve products or perform operations like creating or updating a product.

You can retrieve any registered service in your endpoint using req.scope.resolve passing it the service’s registration name.

Here’s an example of an endpoint that retrieves the count of products in your store:

router.get("/admin/products/count", (req, res) => {
  const productService = req.scope.resolve("productService")

  productService.count().then((count) => {
    res.json({
      count,
    })
  })
})
Enter fullscreen mode Exit fullscreen mode

The productService has a count method that returns a Promise. This Promise resolves to the count of the products. You return a JSON of the product count.

Protected Routes

Protected routes are routes that should be accessible by logged-in users only.

To make a route protected, first, import the authenticate middleware:

import authenticate from "@medusajs/medusa/dist/api/middlewares/authenticate"
Enter fullscreen mode Exit fullscreen mode

Then, add the middleware to your route:

router.get("/store/products/count", authenticate(), (req, res) => {
  //...
})
Enter fullscreen mode Exit fullscreen mode

Now, only authenticated customers or users can access this endpoint.

Accessing Current Customer

You can get the logged-in customer’s ID using req.user:

const id = req.user.customer_id
Enter fullscreen mode Exit fullscreen mode

To get the customer’s details, you can use the customerService:

const id = req.user.customer_id
const customerService = req.scope.resolve("customerService")

const customer = await customerService.retrieve(id)
Enter fullscreen mode Exit fullscreen mode

Accessing Current User

You can get the logged-in user ID using req.user:

const id = req.user.userId
Enter fullscreen mode Exit fullscreen mode

To get the user’s details, you can use the userService:

const id = req.user.userId
const userService = req.scope.resolve("userService")

const user = await userService.retrieve(id)
Enter fullscreen mode Exit fullscreen mode

Route Parameters

The routes you create receive 2 parameters. The first one is the absolute path to the root directory that your server is running from. The second one is an object that has your plugin's options. If your API route is not implemented in a plugin, then it will be an empty object.

export default (rootDirectory, pluginOptions) => {
  const router = Router()

  //...
}
Enter fullscreen mode Exit fullscreen mode

What’s Next?

Endpoints are just one essential part of Medusa’s architecture. Here are more resources to learn more about Medusa’s server and how to customize it:

Should you have any issues or questions related to Medusa, then feel free to reach out to the Medusa team via Discord.

Discussion (2)

Collapse
andrewbaisden profile image
Andrew Baisden

The more I read about Medusa the more I want to try it.

Collapse
shahednasser profile image
Shahed Nasser Author

And you should!