Creating a global production ready marketplace with Medusa JS

Building a scalable marketplace solution can be a complex challenge, especially when trying to balance performance, customization, and a seamless user experience. In this post, I’ll walk you through the journey of creating a Minimum Viable Product (MVP) for a niche marketplace using Medusa, a powerful and extensible e-commerce framework. From selecting the tech stack to implementing custom features like product offers, messaging, and shipping integration, this guide will dive into the technical decisions and solutions that helped bring the marketplace to life. Whether you're starting from scratch or enhancing an existing platform, these insights will be valuable in your own development process.

Published Aug 20, 2024
featured image

The Problem

Recently, at the company where I work, we faced the challenge of building a marketplace solution that could scale and support vast traffic. It’s for a niche market, but the specifications and requirements are quite similar to already available solutions like Vinted, eBay, or Depop. These platforms have established reputations, and the goal for the company was to bring all sellers in their industry together to trade in one place.

The Goal

For the Minimum Viable Product (MVP), there was quite an extensive list of features and capabilities expected, including:

  • Onboarding customers and sellers with full payment provider setup
  • Product listings
  • Product approval flow via the super admin
  • Fast search and product discovery
  • Cart-to-checkout flow
  • Buy now option
  • Offers/product negotiation flow
  • Messaging between seller and buyer
  • Order management and shipping
  • Return flow
  • Notifications throughout the system
  • Opt-in or opt-out of certain features like offers and returns

Selecting the Tech Stack

As this blog focuses primarily on backend technology, I will keep the discussion limited to backend stacks.

Based on project needs and our proficiency in Node.js (Typescript), we selected it for backend API development. The main question was whether to write the API from scratch or extend an existing solution.

Weighing the two options:

  • Writing the system from scratch (using NestJS) provides full control, allowing us to add features like messaging directly into the system while avoiding unnecessary bloat.
  • Using an existing solution, like Medusa JS, a highly extensible e-commerce platform, allows us to leverage a battle-tested e-commerce flow and only extend it for custom logic and additional marketplace-specific features. However, this comes with the tradeoff of unused features that are not needed by the marketplace.

Decision to Use Medusa

In the end, we decided to pick Medusa for the following reasons:

  • Written in Typescript
  • Uses ORMs we're familiar with (TypeORM)
  • Highly extensible and customizable
  • Well-documented
  • Modular architecture in V2 (coming soon), which helps address the issue of unused features/modules
  • Great developer adoption and support

The rest of the tech stack was decided based on Medusa, as it integrates seamlessly with many existing adapters.

Selecting the Database – PostgreSQL

PostgreSQL is the primary supported database for Medusa. We initially tried CockroachDB but encountered issues with certain data types, so we opted for the recommended PostgreSQL, which we set up using Google Cloud SQL.

Implementing Authentication

Medusa comes with built-in authentication, but we needed social authentication (at least Google). Fortunately, there is a Medusa plugin for this, which made it easy to set up. We just needed to configure Google credentials and environment variables.

Writing medusa-plugin-marketplace

This being the third marketplace I’ve built with Medusa, I’ve come across things I wished were essentially plug-and-play to transform a fresh Medusa installation into a basic marketplace. So, I decided to consolidate these elements and publish them as a public plugin: medusa-plugin-marketplace.

It includes the following features:

  • Multiple store support in the Medusa store model
  • Store-specific scoping for:
    • Products
    • Orders
    • Shipping options
    • Invites
    • Users
  • Store roles and permissions
  • Middleware to prevent cross-store actions
  • Advanced Stripe module with marketplace logic:
    • Application fee support
    • Payment splitting (e.g., shipping fee goes to the marketplace Stripe account if it handles label printing)
    • Payment split handling on returns
    • Customizable payment intent and capture behavior
    • Stripe account onboarding widget on super admin
  • Order cart splitting for multiple stores
  • Support for filtering public products by store ID

The plugin is still a work-in-progress, and more work is needed to make it customizable to user needs. Please give it a star to support the work.

Cache Service – Redis

Medusa supports both in-memory and Redis caching via plugins. For production use, Redis is recommended, so we opted for Redis.

Event Listeners and Storage

We implemented the Redis event module for Medusa, which is essential for listening to certain events and triggering actions, such as:

  • Deleting products or variants when the quantity is updated
  • Sending notifications (more on that later)
  • Removing products from the search index upon deletion
  • Syncing product categories upon option updates
  • Updating offers when orders are completed, etc.

Fast Search/Product Indexing with Algolia

We implemented Algolia search but found that the existing module wasn’t customizable enough for our business needs. Since our products are volatile and constantly deleted once out of stock, I wrote a custom Algolia search module for Medusa. This reduced the payload saved in the index. Although this approach comes at a cost with increased search traffic, it allowed us to get the MVP to market quickly. However, we are considering switching to Meilisearch in the future due to the costs associated with Algolia.

Choosing Google Cloud for Hosting and Solving Issues with Storage

We opted for Google Cloud App Engine to deploy the backend because of its scalability and integration with other dependencies like the database and storage. Though documentation for deploying Medusa to App Engine is scarce, setting it up was straightforward with the right IAM configurations and a continuous deployment setup using Cloud Build.

For file storage, we used Google Cloud Storage and wrote a custom Medusa file module to access public and private buckets. There was an issue with Medusa’s hardcoded /uploads folder (App Engine only allows writing to /tmp), so we patched it to resolve the issue.

Notification Orchestration System – Novu & Bird

Notifications are a key part of the project. Our requirements included:

  • Email notifications
  • Push notifications
  • In-app notifications
  • Digest or delayed notifications
  • Phone OTP SMS

To meet these needs, we opted for a notification orchestration system. We chose Novu due to its open-source availability and affordable pricing (compared to Knock). For push notifications, we used Expo Notifications, but we encountered issues integrating Bird (formerly MessageBird) with Novu, so we wrote a custom module to handle authentication codes while Novu handles the rest.

Implementing Offers

Another requirement was enabling price negotiation between buyers and sellers. This involved actions like:

  • Accepting an offer
  • Rejecting an offer
  • Making a counteroffer

We stored offer histories and used custom carts to redirect buyers once offers were approved. Notifications were crucial here since offers expire within a set timeframe.

Messaging with Censorship

For messaging, we initially planned a simple email-like system, but the scope expanded to include:

  • Messaging between buyers and sellers
  • Instant message delivery
  • Read receipts
  • Message notifications
  • Hashing of sensitive information like phone numbers, email addresses, and credit cards
  • Censorship of banned words

Error Reporting with Sentry

We used the Medusa Sentry plugin for error reporting. More details can be found in the documentation.

Shipping Provider for Printing Labels: Shippo

The requirements for shipping included:

  • Basic support for fulfillment by sellers
  • Automatic generation of shipping labels
  • Support for multiple providers
  • Customizable shipping prices via the super admin

Although Medusa has a plugin for Webshipper, we opted for Shippo due to its free entry and great documentation. Since there wasn’t a good Shippo plugin for Medusa, we wrote a custom integration, allowing us to customize workflows like setting fixed shipping prices.

Product Approval

During the early stages, we needed a product approval flow where the marketplace could review products before they went live. Fortunately, Medusa offers these statuses out-of-the-box, so we just needed to implement filtering with Algolia and build a custom UI for super admins to review products.

NB

This is just a surface level look at how the system was created and the reasoning behind certain decisions without going into detail of how to implement certain features, if you require more information regarding building out these systems or questions feel free to reach out via email.