Medusa B2B Starter

repository·main·Indexed 19 days ago

https://github.com/medusajs/b2b-starter-medusa

A customizable B2B ecommerce starter built with Medusa 2.0 and Next.js 15. It features advanced business logic including company management, spending limits, and quote/approval workflows. The starter provides guidance on creating custom API routes, admin widgets, scheduled jobs, custom modules, CLI scripts, and integration testing using medusa-test-utils.

Tokens
17.6K
Snippets
68
Records
75
Agent score
65%

What's inside medusa-b2b-starter

  1. Core Features of Medusa B2B Commerce Starter

    main

    The Medusa B2B Commerce Starter provides specialized features for business-to-business commerce, including:

    • Company Management: Customers can manage their company and invite employees.
    • Spending Limits: Company admins can assign spending limits to employees.
    • Bulk add-to-cart: Customers can add multiple product variants to their cart simultaneously.
    • Quote Management: Communication, acceptance, or rejection of quotes between customers and merchants.
    • Order Edit: Merchants can modify orders or quotes (add/remove items, update quantities, and manage prices).
    • Company Approvals: Mandated approvals from company admins before employees can finalize a cart.
    • Merchant Approvals: Approval processes for orders to ensure compliance with business rules before fulfillment.
    • Promotions: Manual and automatic promotions.
    • Free Shipping Nudge: UI component showing progress toward free shipping thresholds.
    • Full Ecommerce Support: Product pages, collections, categories, cart, checkout, user accounts, and order details.
    • Next.js 15 Support: Utilizes App Router, Caching, Server components/actions, Streaming, and Static Pre-Rendering.
  2. How modules work in Medusa

    main
    A module is a package of reusable functionalities that can be integrated into a Medusa application without affecting the overall system. A module consists of a Service (a class containing business logic) and a Module Definition (which registers the service with the Medusa framework). Once registered in medusa-config.js, the module's service is available in the Medusa dependency injection container and can be resolved in other parts of the application, such as API routes.
  3. Create associations between modules using Module Links

    main

    Module Links allow you to form associations between data models belonging to different modules while preserving module isolation. Instead of creating direct foreign key dependencies between modules (which breaks isolation), you define a link that the Medusa framework manages to connect the two entities.

    To create a link, use the defineLink utility from @medusajs/framework/utils. You must pass the linkable properties of the models you wish to associate.

    import HelloModule from "../modules/hello";
    import ProductModule from "@medusajs/product";
    import { defineLink } from "@medusajs/framework/utils";
    
    export default defineLink(
      ProductModule.linkable.product,
      HelloModule.linkable.myCustom
    );
  4. Quickstart: Setup the Medusa B2B Commerce Starter

    main

    Follow these steps to set up both the Medusa backend and the Next.js storefront. This project requires Node 20, Postgres 15, Medusa 2.4, and Next.js 15.

    1. Setup Backend

    Navigate to the backend directory, configure environment variables, install dependencies, and initialize the database with seed data.

    2. Setup Storefront

    Navigate to the storefront directory, configure environment variables, and install dependencies.

    3. Configure Publishable Key

    To connect the storefront to the backend, you must provide a publishable API key:

    1. Log in to the Medusa Admin at http://localhost:9000/app using:
      • Email: admin@test.com
      • Password: supersecret
    2. Navigate to Settings > Publishable API Keys.
    3. Copy the token key for "Webshop".
    4. Open storefront/.env and add the token to the NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY variable.

    4. Run the Project

    Start the backend with yarn dev in the backend folder and the storefront with yarn dev in the storefront folder.

    • Medusa Admin: http://localhost:9000/app
    • Medusa Storefront: http://localhost:8000
    # Clone the repository
    git clone https://github.com/medusajs/b2b-starter-medusa.git
    
    ## Setup Backend
    cd ./backend
    cp .env.template .env
    yarn install
    
    # Install dependencies, setup database & seed data
    yarn install && yarn medusa db:create && yarn medusa db:migrate && yarn run seed && yarn medusa user -e admin@test.com -p supersecret -i admin
    
    # Start Medusa project - backend & admin
    yarn dev
    
    ## Setup Storefront
    cd ../storefront
    cp .env.template .env
    yarn install
    
    # After setting NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY in storefront/.env
    yarn dev
  5. Use path parameters in API Routes

    main

    To accept path parameters, create a directory within the route's path using the [param] syntax. You can nest multiple parameter directories to accept multiple parameters.

    • Single parameter: /api/products/[productId]/route.ts
    • Multiple parameters: /api/products/[productId]/variants/[variantId]/route.ts

    Access parameters via req.params.

    import type { MedusaRequest, MedusaResponse } from "@medusajs/medusa";
    
    export async function GET(req: MedusaRequest, res: MedusaResponse) {
      const { productId } = req.params;
    
      res.json({
        message: `You're looking for product ${productId}`,
      });
    }
  6. Configure Middleware for API Routes

    main

    To apply middleware to specific routes, create a /api/middlewares.ts file. This file must export a configuration object using defineMiddlewares.

    Each route configuration in the routes array requires:

    • matcher: A string or regular expression defining the route to match.
    • middlewares: An array of middleware functions (req, res, next) => void.
    import { defineMiddlewares } from "@medusajs/medusa";
    import type {
      MedusaRequest,
      MedusaResponse,
      MedusaNextFunction,
    } from "@medusajs/medusa";
    
    async function logger(
      req: MedusaRequest,
      res: MedusaResponse,
      next: MedusaNextFunction
    ) {
      console.log("Request received");
      next();
    }
    
    export default defineMiddlewares({
      routes: [
        {
          matcher: "/store/custom",
          middlewares: [logger],
        },
      ],
    });
  7. Update the B2B Starter Project

    main

    When updating this starter to a newer version, follow these steps:

    Update Packages

    Run yarn install in both the backend and storefront projects to update dependencies to their latest versions.

    Run Migrations

    To apply changes to data models, run the following command in the backend project: npx medusa db:migrate

    Migration for Approval Module

    If you are updating from a version that did not include the Approval module, you must run this script in the backend project to add approval settings to all existing companies: npx medusa exec src/scripts/create-approval-settings.ts

    # In the backend project
    npx medusa db:migrate
    
    # If migrating from a version without the Approval module
    npx medusa exec src/scripts/create-approval-settings.ts
  8. Pass arguments to custom CLI scripts

    main

    You can pass command-line arguments to your script. These arguments are accessible within your exported function via the args property of the ExecArgs object.

    To pass arguments, append them to the npx medusa exec command after the file path.

    import { ExecArgs } from "@medusajs/framework/types";
    
    export default async function myScript({ args }: ExecArgs) {
      console.log(`The arguments you passed: ${args}`);
    }
    npx medusa exec ./src/scripts/my-script.ts arg1 arg2
  9. Create integration tests for API routes using medusa-test-utils

    main

    The medusa-test-utils package provides the medusaIntegrationTestRunner utility to facilitate integration testing for API routes and workflows.

    To use it, call medusaIntegrationTestRunner and provide a testSuite function. This function receives an object containing:

    • api: An object used to make HTTP requests (e.g., api.get, api.post) to your Medusa instance.
    • getContainer: A function to access the Medusa dependency injection container.

    Inside the testSuite, you can use standard testing framework globals like describe and it to structure your tests and assertions.

    import { medusaIntegrationTestRunner } from "medusa-test-utils"
    
    medusaIntegrationTestRunner({
      testSuite: ({ api, getContainer }) => {
        describe("Custom endpoints", () => {
          describe("GET /store/custom", () => {
            it("returns correct message", async () => {
              const response = await api.get(
                `/store/custom`
              )
      
              expect(response.status).toEqual(200)
              expect(response.data).toHaveProperty("message")
              expect(response.data.message).toEqual("Hello, World!")
            })
          })
        })
      }
    })
  10. How to create a custom CLI script

    main

    Custom CLI scripts allow you to execute custom Medusa tooling via the Medusa CLI. To create one, create a TypeScript or JavaScript file inside the src/scripts directory. The file must provide a default export of an asynchronous function.

    The function receives an ExecArgs object as its parameter, which contains a container property. This container is an instance of the Medusa Container, allowing you to resolve services and modules (e.g., using container.resolve()) within your script.

    import { ExecArgs, IProductModuleService } from "@medusajs/framework/types";
    import { ModuleRegistrationName } from "@medusajs/framework/utils";
    
    export default async function myScript({ container }: ExecArgs) {
      const productModuleService: IProductModuleService = container.resolve(
        ModuleRegistrationName.PRODUCT
      );
    
      const [, count] = await productModuleService.listAndCount();
    
      console.log(`You have ${count} product(s)`);
    }