dist/cursor/medusa-commerce/skills/node-backend/SKILL.md
Build Node.js backends for Medusa v2 — Express middleware chain, MikroORM entity patterns, PostgreSQL connection pooling, async patterns, error handling, and logging. Use when working with Medusa's Node.js runtime.
npx skillsauth add orcaqubits/agentic-commerce-claude-plugins node-backendInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fetch live docs:
site:docs.medusajs.com api routes middlewares express for middleware configurationsite:mikro-orm.io docs for MikroORM entity and query patternssite:docs.medusajs.com error handling MedusaError for error typeshttps://docs.medusajs.com/learn/fundamentals/api-routes for API route conventionssite:node.org docs async_hooks performance for Node.js async best practicesMedusa v2 is built on Express. Every HTTP request flows through a defined middleware chain:
Incoming Request
├── 1. CORS middleware
├── 2. Body parser (JSON)
├── 3. Cookie parser / session
├── 4. Authentication middleware
├── 5. Custom middlewares (middlewares.ts)
├── 6. Route handler (route.ts)
└── 7. Error handler
Define custom middleware in src/api/middlewares.ts:
// Fetch live docs for defineMiddlewares
// and MiddlewareRoute type signatures
import { defineMiddlewares } from "@medusajs/medusa"
| Middleware Property | Purpose |
|--------------------|---------|
| matcher | Route pattern to match (glob or regex) |
| method | HTTP method filter (GET, POST, etc.) |
| middlewares | Array of Express middleware functions |
| bodyParser | Configure or disable body parsing |
| authenticate | Apply auth requirements |
Middleware executes in the order defined in the routes array. Global middleware (matcher *) runs before route-specific middleware.
Medusa v2 abstracts MikroORM behind DML. However, understanding the underlying ORM helps with advanced queries:
| DML Layer | MikroORM Layer | Purpose |
|-----------|---------------|---------|
| model.define() | @Entity() decorator | Define database entity |
| DML fields | @Property() decorators | Column definitions |
| DML relationships | @OneToMany(), @ManyToOne() | Relationship mappings |
| DML .index() | @Index() decorator | Database indexes |
| Generated migrations | MikroORM migration files | Schema changes |
Module services generated by MedusaService provide typed query methods:
| Operation | Service Method | Underlying ORM Action |
|-----------|---------------|----------------------|
| Find many | list(filters, config) | em.find() with criteria |
| Find one | retrieve(id, config) | em.findOneOrFail() |
| Create | create(data) | em.create() + em.persist() |
| Update | update(data) | em.assign() + em.flush() |
| Delete | delete(id) | em.removeAndFlush() |
| Soft delete | softDelete(id) | Set deleted_at timestamp |
MikroORM uses the Unit of Work pattern — changes are tracked and flushed together per request.
Medusa uses MikroORM's built-in PostgreSQL driver with connection pooling:
| Setting | Default | Production Recommendation |
|---------|---------|--------------------------|
| Pool minimum | 2 | 5-10 |
| Pool maximum | 10 | 20-50 (based on load) |
| Idle timeout | 30s | 10-30s |
| Connection timeout | 5s | 5-10s |
| SSL mode | Disabled | require or verify-full |
DATABASE_URL query parameters or medusa-config.ts database optionspg_stat_activitymax_connections (reserve for admin)| Pattern | Use Case | Avoid |
|---------|----------|-------|
| async/await | Sequential async operations | Callback-based patterns |
| Promise.all() | Parallel independent operations | Sequential awaits for independent tasks |
| Promise.allSettled() | Parallel ops where some may fail | Promise.all() when partial success is OK |
| for await...of | Async iterators, streams | Manual iterator consumption |
| try/catch in async | Error handling in async functions | Unhandled promise rejections |
Workflow steps are inherently async. Each step receives context and returns a StepResponse:
// Fetch live docs for createStep async
// signature and StepResponse typing
Subscribers handle events asynchronously. They execute in the worker process when running in separate mode:
| Concept | Implementation |
|---------|---------------|
| Event emission | Automatic on entity changes or manual via event bus |
| Subscriber registration | Export config with event and handler function |
| Execution context | Runs in worker process (async from request) |
| Error handling | Subscriber errors do not affect the triggering request |
| Error Class | HTTP Status | Use Case |
|------------|-------------|----------|
| MedusaError | Varies | Base error class with type codes |
| NOT_FOUND | 404 | Entity not found |
| INVALID_DATA | 400 | Validation failure |
| UNAUTHORIZED | 401 | Authentication required |
| NOT_ALLOWED | 403 | Insufficient permissions |
| CONFLICT | 409 | Duplicate or conflicting state |
| UNEXPECTED_STATE | 500 | Internal logic error |
// Fetch live docs for MedusaError import
// and error type enum values
import { MedusaError } from "@medusajs/framework/utils"
MedusaError with appropriate type in service methodstry/catch in workflow steps and return compensation data on failureMedusa provides a structured logger accessible via the DI container:
| Log Level | Method | Use Case |
|-----------|--------|----------|
| error | logger.error() | Unrecoverable errors, exceptions |
| warn | logger.warn() | Degraded state, deprecated usage |
| info | logger.info() | Significant events, startup, shutdown |
| debug | logger.debug() | Detailed diagnostic information |
Resolve the logger from the container via container.resolve("logger"). Log at appropriate levels, include contextual data (entity IDs, operation names), and configure structured JSON output in production.
| Concern | Recommendation |
|---------|---------------|
| Node.js version | Use Node.js 20+ (LTS) for Medusa v2 |
| Memory | Set --max-old-space-size for large catalogs |
| Event loop | Avoid blocking the event loop with synchronous operations |
| Graceful shutdown | Handle SIGTERM to close connections and finish in-flight requests |
| Clustering | Use multiple server instances (not Node cluster) behind a load balancer |
src/api/middlewares.ts; order middleware from general to specific; use route matchers to scope middleware narrowlylist with filters instead of raw queries; leverage soft deletes for data preservationawait async operations; use Promise.all for parallel independent work; handle errors in every try/catch block with meaningful error typesconfig.relations to eager-load relationshipsFetch the Medusa API route and middleware documentation for exact handler signatures, error types, and logging configuration before implementing.
development
Build with Spree's headless Next.js storefront — the official `spree/storefront` repo (Next.js 16 App Router with Server Actions and Turbopack, React 19 Server Components, Tailwind CSS 4, TypeScript 5, `@spree/sdk`, Sentry), server-only auth (httpOnly JWT cookies + publishable key), MeiliSearch faceted catalog, one-page checkout with Apple/Google Pay/Klarna/Affirm/SEPA, multi-region market routing, GA4 + JSON-LD SEO, and Vercel/Docker deployment. Use when forking or customizing the storefront, or evaluating headless adoption.
tools
Build Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies for swapping service objects, gem release/versioning, and the deprecated Deface engine. Use when building a reusable Spree extension or adding non-trivial customization to an app.
development
Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.
tools
Cross-cutting Spree development patterns — the customization preference hierarchy (Events > Webhooks > Dependencies > Decorators), `Spree::Dependencies` service-object swapping, the `_decorator.rb` + `prepend` + `self.prepended` idiom, idempotent subscribers and webhook receivers, multi-store scoping discipline, prefixed IDs, calculator polymorphism (shipping/promotion/tax share the base), service-object composition with `dry-monads` or simple results, why to avoid `class_eval` reopening and Deface, and Spree-on-Rails idioms (Hotwire/Turbo Stimulus, ActiveStorage, Action Cable, Sidekiq). Use when designing the architecture of a Spree extension or solving cross-cutting concerns.