# Best Practices for Structuring a Medusa Project: A Complete Developer Guide

> Learn best practices for structuring Medusa projects with a TypeScript monorepo. Explore clear separation for scalable commerce apps following official conventions.

- Repository: [Medusa/medusa](https://github.com/medusajs/medusa)
- Tags: best-practices
- Published: 2026-05-19

---

**The optimal Medusa project structure follows a TypeScript monorepo layout with clear separation between core runtime, modules, admin UI, and CLI tools under a `packages/` directory, enabling scalable commerce applications that align with Medusa's official conventions.**

Medusa is a modular commerce engine maintained as a TypeScript monorepo that groups all core runtime, modules, admin UI, and CLI tools under the `packages/` folder. Following the structural conventions established in the official `medusajs/medusa` repository ensures your project remains maintainable, compatible with automated tooling, and straightforward to upgrade. This guide maps the exact directory patterns, service architectures, and configuration strategies used by the core team.

## Adopt the Official Monorepo Layout

The Medusa repository organizes code into distinct top-level directories that separate concerns and enable Yarn 3 workspaces to handle dependency hoisting automatically. Replicating this structure in your own projects ensures compatibility with Medusa's module system and upgrade path.

| Directory | Purpose | Reference |
|-----------|---------|-----------|
| `packages/medusa` | Main server (API routes, DB config, core services) | [`packages/medusa/README.md`](https://github.com/medusajs/medusa/blob/main/packages/medusa/README.md) |
| `packages/core` | Framework utilities, workflow engine, types, and core-flows | [`packages/core/framework/README.md`](https://github.com/medusajs/medusa/blob/main/packages/core/framework/README.md) |
| `packages/modules/*` | Individual commerce modules (product, order, cart, payment) | [`packages/modules/index/README.md`](https://github.com/medusajs/medusa/blob/main/packages/modules/index/README.md) |
| `packages/admin/*` | Admin dashboard UI and SDK | [`packages/admin/dashboard/README.md`](https://github.com/medusajs/medusa/blob/main/packages/admin/dashboard/README.md) |
| `packages/cli/*` | CLI helpers (`medusa-dev`, `create-medusa-app`, OAS generator) | [`packages/cli/create-medusa-app/README.md`](https://github.com/medusajs/medusa/blob/main/packages/cli/create-medusa-app/README.md) |
| `www/` | Documentation site (book, resources, API reference) | [`www/README.md`](https://github.com/medusajs/medusa/blob/main/www/README.md) |
| `integration-tests/` | End-to-end integration tests that spin up a full Medusa stack | [`integration-tests/README.md`](https://github.com/medusajs/medusa/blob/main/integration-tests/README.md) |

This layout provides **clear separation of concerns**, allowing you to enable or disable modules via the [`medusa-config.js`](https://github.com/medusajs/medusa/blob/main/medusa-config.js) file without affecting unrelated systems. It also ensures **future-proof upgrades**, as the Medusa team adds new core-flows or modules without breaking existing projects that adhere to these boundaries.

## Bootstrap New Projects with create-medusa-app

The official starter generates a ready-to-run workspace that mirrors the monorepo structure. Running this command is the first step in every new Medusa project:

```bash
npx create-medusa-app@latest my-store
cd my-store
yarn install
yarn dev   # starts the API server, admin UI and DB migrations

```

The CLI source that wires these generated files together lives in [`packages/cli/create-medusa-app/src/index.ts`](https://github.com/medusajs/medusa/blob/main/packages/cli/create-medusa-app/src/index.ts).

## Organize Domain Logic in Custom Modules

When adding domain-specific logic (such as a custom fulfillment provider or payment gateway), create a **module** under `packages/modules/` following the same pattern as built-in modules.

### Module Structure

1. Create a folder `my-custom-module` inside `packages/modules/`.
2. Export a service class that extends `MedusaService<T>` and uses the decorator suite (`@InjectManager`, `@InjectTransactionManager`, `@EmitEvents`).

```typescript
// packages/modules/my-custom-module/src/services/custom-service.ts
import {
  MedusaService,
  InjectManager,
  MedusaContext,
  EmitEvents,
} from "@medusajs/framework/utils"

export class CustomService extends MedusaService<{ MyEntity: { dto: any } }>({
  MyEntity,
}) {
  @InjectManager()
  @EmitEvents()
  async doSomething(
     data: any,
     @MedusaContext() ctx: Context = {}
  ) {
     // business logic here
  }
}

```

The **order module** demonstrates this pattern in [`packages/modules/order/src/services/order-module-service.ts`](https://github.com/medusajs/medusa/blob/main/packages/modules/order/src/services/order-module-service.ts).

3. Register the module in [`medusa-config.js`](https://github.com/medusajs/medusa/blob/main/medusa-config.js):

```javascript
module.exports = {
  modules: [
    {
      resolve: "@medusajs/module-my-custom",
      options: { /* module-specific config */ },
    },
  ],
}

```

Module registration logic lives in the core framework at [`packages/core/framework/src/modules/registration.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/framework/src/modules/registration.ts).

**Best practice:** Keep each custom module **self-contained** with its own DTOs, migrations, and tests. This mirrors the built-in modules and lets you reuse workflow patterns across your application.

## Implement Services and Workflows

Medusa's architecture separates data access (services) from business process orchestration (workflows).

| Layer | File Pattern | Key Concepts |
|-------|-------------|--------------|
| Service | `src/services/*-service.ts` | Uses `@InjectManager`, `@InjectTransactionManager`, `@EmitEvents` |
| Workflow step | `src/steps/*-step.ts` | Created with `createStep(id, main, compensation?)` |
| Workflow definition | `src/workflows/*-workflow.ts` | Built with `createWorkflow`, returns `WorkflowResponse` |

For example, deleting a promotion uses this exact pattern in [`packages/core/core-flows/src/promotion/steps/delete-promotions.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/core-flows/src/promotion/steps/delete-promotions.ts) and [`packages/core/core-flows/src/promotion/workflows/delete-promotions.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/core-flows/src/promotion/workflows/delete-promotions.ts).

When adding new business logic, **reuse** these patterns rather than writing ad-hoc database calls. This guarantees automatic event emission, transaction handling, and compensation (rollback) support.

## Structure API Routes by Domain

All HTTP endpoints live under `packages/medusa/src/api/`. The naming convention distinguishes between administrative and storefront operations:

- **Admin routes** – prefixed with `/admin/` and exported as `GET`, `POST`, `PUT`, `DELETE`, `PATCH`
- **Store routes** – prefixed with `/store/`

A typical route file aggregates HTTP methods:

```typescript
// packages/medusa/src/api/admin/orders/route.ts
import { DELETE } from "./delete"
import { GET } from "./get"

export const routes = {
  GET,
  DELETE,
}

```

The order admin route at [`packages/medusa/src/api/admin/orders/route.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa/src/api/admin/orders/route.ts) and the payment-collection route at `packages/medusa/src/api/admin/payment-collections/[id]/route.ts` provide reference implementations.

**Best practices:**
- Keep request validation in the route file using `AuthenticatedMedusaRequest`.
- Delegate all business logic to a **workflow** rather than calling services directly.
- Return plain JSON objects matching types defined in `packages/core/types/src/http`.

## Maintain Code Quality with Testing

Medusa ships with **unit tests** (`*.spec.ts`) next to each source file and **integration tests** under `integration-tests/`.

1. **Unit-test** each service method by mocking the DAL and verifying business rules.
2. **Workflow-test** using the `createWorkflow` test harness (see [`packages/core/workflows-sdk/src/__tests__/workflow.test.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/workflows-sdk/src/__tests__/workflow.test.ts)).
3. **Integration-test** the full HTTP flow (see [`integration-tests/http/__tests__/order.test.ts`](https://github.com/medusajs/medusa/blob/main/integration-tests/http/__tests__/order.test.ts)).

Run the test suites:

```bash
yarn test               # unit tests only

yarn test:integration   # full stack tests

```

Test configuration details appear in [`packages/core/framework/README.md`](https://github.com/medusajs/medusa/blob/main/packages/core/framework/README.md).

## Configure Production Logging and Error Handling

The framework provides structured logging (`logger.info`, `logger.error`) and the `MedusaError` class with standardized error types (`NOT_FOUND`, `INVALID_DATA`, `NOT_ALLOWED`). Follow these patterns in every custom module:

```typescript
if (!entity) {
  throw new MedusaError(
    MedusaError.Types.NOT_FOUND,
    `Entity with id ${id} not found`
  )
}

```

Error handling examples appear in [`packages/core/utils/src/modules-sdk/medusa-internal-service.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/utils/src/modules-sdk/medusa-internal-service.ts).

## Summary

- **Mirror the monorepo layout** by organizing code into `packages/medusa`, `packages/modules`, and `packages/core` to ensure compatibility with Medusa's tooling.
- **Generate new projects** using `create-medusa-app` to inherit the correct directory structure and configuration files.
- **Build custom modules** as self-contained units extending `MedusaService` and register them in [`medusa-config.js`](https://github.com/medusajs/medusa/blob/main/medusa-config.js).
- **Orchestrate business logic** using the `createStep` and `createWorkflow` APIs rather than ad-hoc service calls.
- **Follow routing conventions** by placing admin endpoints under `/admin/` and store endpoints under `/store/` with logic delegated to workflows.
- **Test at multiple layers** with unit tests co-located with source files and integration tests in the `integration-tests/` directory.
- **Use standardized error handling** via `MedusaError` and structured logging provided by the core framework.

## Frequently Asked Questions

### What is the recommended way to start a new Medusa project?

Use the official CLI tool `create-medusa-app` by running `npx create-medusa-app@latest my-store`. This bootstraps a TypeScript monorepo with the correct directory structure, dependency configuration, and example modules that align with the `medusajs/medusa` repository conventions.

### How do I add custom business logic to a Medusa project?

Create a custom module under `packages/modules/` containing a service class that extends `MedusaService`. Implement your logic using decorators like `@InjectManager` and `@EmitEvents`, then register the module in [`medusa-config.js`](https://github.com/medusajs/medusa/blob/main/medusa-config.js). For multi-step processes, wrap your logic in a workflow using `createStep` and `createWorkflow` from the workflows SDK.

### Where should I place API endpoint definitions in a Medusa project?

Define routes in `packages/medusa/src/api/` using subdirectories for `admin/` and `store/` prefixes. Each route file should export HTTP methods (GET, POST, etc.) and delegate business logic to workflows rather than calling services directly. Reference the order routes at [`packages/medusa/src/api/admin/orders/route.ts`](https://github.com/medusajs/medusa/blob/main/packages/medusa/src/api/admin/orders/route.ts) for the standard pattern.

### How does Medusa handle database transactions and error management?

Medusa uses decorators like `@InjectManager` and `@InjectTransactionManager` to handle database transactions automatically within service methods. For errors, use the `MedusaError` class with standardized types such as `NOT_FOUND` or `INVALID_DATA`, which ensures consistent error formatting across your application as shown in [`packages/core/utils/src/modules-sdk/medusa-internal-service.ts`](https://github.com/medusajs/medusa/blob/main/packages/core/utils/src/modules-sdk/medusa-internal-service.ts).