Unchained vs MedusaJS
A technical deep‑dive comparing two open‑source e‑commerce engines. GraphQL‑first vs REST‑first, MongoDB vs PostgreSQL, EUPL vs MIT — and the built‑in features that set them apart.
Architecture at a Glance
Two open‑source engines, two fundamentally different approaches to building e‑commerce backends.
Feature‑by‑Feature Comparison
Side‑by‑side breakdown of capabilities across core, built‑in features, enterprise, and infrastructure.
| Feature | Unchained | MedusaJS |
|---|---|---|
| Core | ||
| License | EUPL (Copyleft) | MIT |
| Primary API | GraphQL‑first | REST‑first |
| Database | MongoDB | PostgreSQL |
| Language | Node.js + TypeScript | Node.js + TypeScript |
| Built‑in Features | ||
| Subscriptions / Enrollments | ||
| Quotations / RFQ | ||
| Event Ticketing | ||
| Product Catalog | ||
| Order Management | ||
| Payment Integrations | ||
| Enterprise | ||
| B2B Features | Advanced | Basic |
| MCP / AI Integration | ||
| OCSF Audit Trail | ||
| WebAuthn / Passkeys | ||
| Infrastructure | ||
| Docker‑native | ||
| Horizontal Scaling | Stateless | Stateless |
| NPM Packages | 28 packages | Monorepo modules |
GraphQL vs REST
Unchained exposes a GraphQL API by default, letting you fetch exactly the data you need in a single request. MedusaJS uses REST endpoints, often requiring multiple round‑trips for the same data.
// Single GraphQL query — products with variants, pricing & stock
const { data } = await client.query({
query: gql`
query Products($limit: Int) {
products(limit: $limit) {
_id
texts { title description }
simulatedPrice(currency: "CHF") {
price { amount currency }
}
catalogChildren {
_id
texts { title }
simulatedStocks { quantity }
}
}
}
`,
variables: { limit: 10 }
});// Multiple REST calls required for the same data
const products = await fetch('/store/products?limit=10')
.then(r => r.json());
// For each product, fetch variants separately
const variants = await Promise.all(
products.map(p =>
fetch(`/store/products/${p.id}/variants`)
.then(r => r.json())
)
);
// Prices require another endpoint
const prices = await fetch('/store/pricing/calculate', {
method: 'POST',
body: JSON.stringify({
product_ids: products.map(p => p.id),
currency_code: 'chf'
})
}).then(r => r.json());
// Stock levels from inventory module
const stock = await fetch('/admin/inventory-items')
.then(r => r.json());Type Safety
GraphQL schemas provide compile‑time type checking and auto‑generated TypeScript types for every query.
Fewer Round‑Trips
Fetch products, variants, prices, and stock in one request instead of chaining multiple REST calls.
Introspection
The schema is self‑documenting. Tools like GraphQL Playground let you explore the entire API interactively.
Database Architecture
Different storage engines reflect different trade‑offs: schema flexibility vs relational integrity.
MongoDB (Unchained)
- Flexible document schemas — embed variants, prices, and metadata directly inside product documents
- Schema‑less evolution — add fields without migrations
- Horizontal scaling with native sharding
- PostgreSQL compatible via FerretDB when DB is not a choice
PostgreSQL (MedusaJS)
- ACID transactions for every write
- Normalized relational model with strong referential integrity
- Mature ecosystem of extensions (PostGIS, pg_trgm, etc.)
- JOIN‑based queries across orders, products, and customers
Plugin & Module System
Both platforms are extensible, but they take different architectural approaches to customization.
Both Follow DDD Principles
Both engines apply domain‑driven design with the Open/Closed Principle, layered architecture, adapters & factories, and dependency inversion. The core patterns are similar — the differences lie in naming and implementation details.
Unchained: Adapter‑Based Plugins
Each concern (payments, delivery, messaging, warehousing) has a clearly defined adapter interface. Swap providers by registering a different adapter — no core changes required.
MedusaJS: Service/Repository Pattern
Uses services, repositories, and subscribers. Modules are organized in a monorepo and extend the core through dependency injection and lifecycle hooks.
import { startPlatform } from '@unchainedshop/platform';
import { stripePayments } from '@unchainedshop/plugins';
await startPlatform({
modules: {
// Adapter-based plugin system
payments: {
adapters: [stripePayments],
},
delivery: {
adapters: [postDelivery],
},
messaging: {
adapters: [sendgridMessaging],
},
warehousing: {
adapters: [customWarehouse],
},
},
});Built‑in Enterprise Features
Features that Unchained ships natively — no plugins, no third‑party services, no extra cost.
Subscriptions & Enrollments
Recurring billing, membership management, and enrollment workflows built into the core engine.
Quotations & RFQ
B2B negotiation workflows with request‑for‑quote, approval chains, and custom pricing per customer.
Event Ticketing
Apple Wallet and Google Wallet passes, QR code generation, and check‑in management out of the box.
MCP Server
Native AI assistant integration via the Model Context Protocol. Let AI agents browse products, manage carts, and place orders.
OCSF Audit Trail
Compliance‑grade logging following the Open Cybersecurity Schema Framework for full operational transparency.
WebAuthn / Passkeys
Passwordless authentication with biometric and hardware key support. FIDO2‑compliant and phishing‑resistant.
Ready to Build with Unchained?
Start building your next e‑commerce project on a GraphQL‑first, enterprise‑ready platform licensed under the EUPL.