name: medusa
description: Medusa 无头电商 - 模块、工作流、API路由、管理UI
Medusa E-Commerce 技能
加载方式:base.md + typescript.md
用于构建无头电商的 Medusa - 开源、Node.js 原生、完全可定制。
来源: Medusa 文档 | API 参考 | GitHub
为什么选择 Medusa
| 特性 |
好处 |
| 开源 |
自主托管,无供应商锁定,MIT 许可证 |
| Node.js 原生 |
TypeScript,熟悉的技术栈,易于定制 |
| 无头 |
任何前端(Next.js、Remix、移动设备) |
| 模块化 |
按需使用,扩展任何功能 |
| 内置管理 |
包含仪表板,可定制 |
快速开始
先决条件
# 必需
node --version # v20+ LTS
git --version
# 运行本地或远程的 PostgreSQL
创建新项目
# 搭建新的 Medusa 应用
npx create-medusa-app@latest my-store
# 创建:
# - Medusa 后端
# - PostgreSQL 数据库(自动配置)
# - 管理仪表板
# - 可选:Next.js 商店前端
cd my-store
npm run dev
访问点
| URL |
目的 |
http://localhost:9000 |
后端 API |
http://localhost:9000/app |
管理仪表板 |
http://localhost:8000 |
商店前端(如果安装) |
创建管理用户
npx medusa user -e admin@example.com -p supersecret
项目结构
medusa-store/
├── src/
│ ├── admin/ # 管理 UI 自定义
│ │ ├── widgets/ # 仪表板组件
│ │ └── routes/ # 自定义管理页面
│ ├── api/ # 自定义 API 路由
│ │ ├── store/ # 公共商店 API
│ │ │ └── custom/
│ │ │ └── route.ts
│ │ └── admin/ # 管理 API
│ │ └── custom/
│ │ └── route.ts
│ ├── jobs/ # 定时任务
│ ├── modules/ # 自定义业务逻辑
│ ├── workflows/ # 多步骤流程
│ ├── subscribers/ # 事件监听器
│ └── links/ # 模块关系
├── .medusa/ # 自动生成(不要编辑)
├── medusa-config.ts # 配置
├── package.json
└── tsconfig.json
配置
medusa-config.ts
import { defineConfig, loadEnv } from "@medusajs/framework/utils";
loadEnv(process.env.NODE_ENV || "development", process.cwd());
export default defineConfig({
projectConfig: {
databaseUrl: process.env.DATABASE_URL,
http: {
storeCors: process.env.STORE_CORS || "http://localhost:8000",
adminCors: process.env.ADMIN_CORS || "http://localhost:9000",
authCors: process.env.AUTH_CORS || "http://localhost:9000",
},
redisUrl: process.env.REDIS_URL,
},
admin: {
disable: false,
backendUrl: process.env.MEDUSA_BACKEND_URL || "http://localhost:9000",
},
modules: [
// 添加自定义模块
],
});
环境变量
# .env
DATABASE_URL=postgresql://user:pass@localhost:5432/medusa
REDIS_URL=redis://localhost:6379
# CORS(多个来源用逗号分隔)
STORE_CORS=http://localhost:8000
ADMIN_CORS=http://localhost:9000
# 后端 URL
MEDUSA_BACKEND_URL=http://localhost:9000
# JWT 密钥
JWT_SECRET=your-super-secret-jwt-key
COOKIE_SECRET=your-super-secret-cookie-key
自定义 API 路由
商店 API(公共)
// src/api/store/hello/route.ts
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
res.json({
message: "Hello from custom store API!",
});
}
// 可在:GET /store/hello 访问
管理 API(受保护)
// src/api/admin/analytics/route.ts
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
import { Modules } from "@medusajs/framework/utils";
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
const orderService = req.scope.resolve(Modules.ORDER);
const orders = await orderService.listOrders({
created_at: {
$gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // 上个月
},
});
const totalRevenue = orders.reduce(
(sum, order) => sum + (order.total || 0),
0
);
res.json({
orderCount: orders.length,
totalRevenue,
});
}
// 可在:GET /admin/analytics(需要认证)访问
带参数的路由
// src/api/store/products/[id]/reviews/route.ts
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
export async function GET(
req: MedusaRequest,
res: MedusaResponse
) {
const { id } = req.params;
// 获取产品的评论
const reviews = await getReviewsForProduct(id);
res.json({ reviews });
}
export async function POST(
req: MedusaRequest,
res: MedusaResponse
) {
const { id } = req.params;
const { rating, comment, customerId } = req.body;
const review = await createReview({
productId: id,
rating,
comment,
customerId,
});
res.status(201).json({ review });
}
// 可在:
// GET /store/products/:id/reviews
// POST /store/products/:id/reviews 访问
中间件
// src/api/middlewares.ts
import { defineMiddlewares } from "@medusajs/framework/http";
import { authenticate } from "@medusajs/framework/http";
export default defineMiddlewares({
routes: [
{
matcher: "/store/protected/*",
middlewares: [authenticate("customer", ["session", "bearer"])],
},
{
matcher: "/admin/*",
middlewares: [authenticate("user", ["session", "bearer"])],
},
],
});
模块(自定义业务逻辑)
创建自定义模块
// src/modules/reviews/index.ts
import { Module } from "@medusajs/framework/utils";
import ReviewModuleService from "./service";
export const REVIEW_MODULE = "reviewModuleService";
export default Module(REVIEW_MODULE, {
service: ReviewModuleService,
});
// src/modules/reviews/service.ts
import { MedusaService } from "@medusajs/framework/utils";
class ReviewModuleService extends MedusaService({}) {
async createReview(data: CreateReviewInput) {
// 实现
}
async getProductReviews(productId: string) {
// 实现
}
async getAverageRating(productId: string) {
// 实现
}
}
export default ReviewModuleService;
注册模块
// medusa-config.ts
import { REVIEW_MODULE } from "./src/modules/reviews";
export default defineConfig({
// ...
modules: [
{
resolve: "./src/modules/reviews",
options: {},
},
],
});
在 API 中使用模块
// src/api/store/products/[id]/reviews/route.ts
import { REVIEW_MODULE } from "../../../modules/reviews";
export async function GET(req: MedusaRequest, res: MedusaResponse) {
const { id } = req.params;
const reviewService = req.scope.resolve(REVIEW_MODULE);
const reviews = await reviewService.getProductReviews(id);
const averageRating = await reviewService.getAverageRating(id);
res.json({ reviews, averageRating });
}
工作流
定义工作流
// src/workflows/create-order-with-notification/index.ts
import {
createWorkflow,
createStep,
StepResponse,
} from "@medusajs/framework/workflows-sdk";
import { Modules } from "@medusajs/framework/utils";
const createOrderStep = createStep(
"create-order",
async (input: CreateOrderInput, { container }) => {
const orderService = container.resolve(Modules.ORDER);
const order = await orderService.createOrders(input);
return new StepResponse(order, order.id);
},
// 补偿(回滚)函数
async (orderId, { container }) => {
const orderService = container.resolve(Modules.ORDER);
await orderService.deleteOrders([orderId]);
}
);
const sendNotificationStep = createStep(
"send-notification",
async (order: Order, { container }) => {
const notificationService = container.resolve("notificationService");
await notificationService.send({
to: order.email,
template: "order-confirmation",
data: { order },
});
return new StepResponse({ sent: true });
}
);
export const createOrderWithNotificationWorkflow = createWorkflow(
"create-order-with-notification",
(input: CreateOrderInput) => {
const order = createOrderStep(input);
const notification = sendNotificationStep(order);
return { order, notification };
}
);
执行工作流
// 在 API 路由中
import { createOrderWithNotificationWorkflow } from "../../../workflows/create-order-with-notification";
export async function POST(req: MedusaRequest, res: MedusaResponse) {
const { result } = await createOrderWithNotificationWorkflow(req.scope).run({
input: req.body,
});
res.json(result);
}
订阅者(事件监听器)
创建订阅者
// src/subscribers/order-placed.ts
import type { SubscriberArgs, SubscriberConfig } from "@medusajs/framework";
export default async function orderPlacedHandler({
event,
container,
}: SubscriberArgs<{ id: string }>) {
const orderId = event.data.id;
console.log(`Order placed: ${orderId}`);
// 发送通知,更新分析等。
const notificationService = container.resolve("notificationService");
await notificationService.sendOrderConfirmation(orderId);
}
export const config: SubscriberConfig = {
event: "order.placed",
};
常见事件
| 事件 |
触发器 |
order.placed |
新订单创建 |
order.updated |
订单修改 |
order.canceled |
订单取消 |
order.completed |
订单完成 |
customer.created |
新客户注册 |
product.created |
新产品添加 |
product.updated |
产品修改 |
inventory.updated |
库存变化 |
定时任务
// src/jobs/sync-inventory.ts
import type { MedusaContainer } from "@medusajs/framework";
export default async function syncInventoryJob(container: MedusaContainer) {
const inventoryService = container.resolve("inventoryService");
console.log("Running inventory sync...");
await inventoryService.syncFromExternalSource();
console.log("Inventory sync complete");
}
export const config = {
name: "sync-inventory",
schedule: "0 */6 * * *", // 每 6 小时
};
管理 UI 自定义
自定义组件
// src/admin/widgets/sales-overview.tsx
import { defineWidgetConfig } from "@medusajs/admin-sdk";
import { Container, Heading, Text } from "@medusajs/ui";
const SalesOverviewWidget = () => {
return (
<Container>
<Heading level="h2">Sales Overview</Heading>
<Text>自定义销售数据...</Text>
</Container>
);
};
export const config = defineWidgetConfig({
zone: "order.list.before", // 显示组件的位置
});
export default SalesOverviewWidget;
组件区域
| 区域 |
位置 |
order.list.before |
订单列表之前 |
order.details.after |
订单详情之后 |
product.list.before |
产品列表之前 |
product.details.after |
产品详情之后 |
customer.list.before |
客户列表之前 |
自定义管理路由
// src/admin/routes/analytics/page.tsx
import { defineRouteConfig } from "@medusajs/admin-sdk";
import { Container, Heading } from "@medusajs/ui";
import { ChartBar } from "@medusajs/icons";
const AnalyticsPage = () => {
return (
<Container>
<Heading level="h1">Analytics Dashboard</Heading>
{/* 你的分析图表 */}
</Container>
);
};
export const config = defineRouteConfig({
label: "Analytics",
icon: ChartBar,
});
export default AnalyticsPage;
商店 API(内置)
产品
// 前端:获取产品
const response = await fetch("http://localhost:9000/store/products");
const { products } = await response.json();
// 带过滤器
const response = await fetch(
"http://localhost:9000/store/products?" +
new URLSearchParams({
category_id: "cat_123",
limit: "20",
offset: "0",
})
);
购物车
// 创建购物车
const { cart } = await fetch("http://localhost:9000/store/carts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
region_id: "reg_123",
}),
}).then(r => r.json());
// 添加商品
await fetch(`http://localhost:9000/store/carts/${cart.id}/line-items`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
variant_id: "variant_123",
quantity: 1,
}),
});
// 完成购物车(创建订单)
const { order } = await fetch(
`http://localhost:9000/store/carts/${cart.id}/complete`,
{ method: "POST" }
).then(r => r.json());
客户认证
// 注册
await fetch("http://localhost:9000/store/customers", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: "customer@example.com",
password: "password123",
first_name: "John",
last_name: "Doe",
}),
});
// 登录
const { token } = await fetch("http://localhost:9000/store/auth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: "customer@example.com",
password: "password123",
}),
}).then(r => r.json());
// 认证请求
await fetch("http://localhost:9000/store/customers/me", {
headers: {
Authorization: `Bearer ${token}`,
},
});
支付集成
Stripe 设置
npm install @medusajs/payment-stripe
// medusa-config.ts
export default defineConfig({
modules: [
{
resolve: "@medusajs/payment-stripe",
options: {
apiKey: process.env.STRIPE_API_KEY,
},
},
],
});
在管理中
- 转到设置 → 区域
- 添加 Stripe 作为支付提供商
- 为每个区域配置
部署
Railway
# 安装 Railway CLI
npm install -g @railway/cli
# 登录和部署
railway login
railway init
railway up
Render
# render.yaml
services:
- type: web
name: medusa-backend
runtime: node
plan: starter
buildCommand: npm install && npm run build
startCommand: npm run start
envVars:
- key: NODE_ENV
value: production
- key: DATABASE_URL
fromDatabase:
name: medusa-db
property: connectionString
- key: JWT_SECRET
generateValue: true
- key: COOKIE_SECRET
generateValue: true
databases:
- name: medusa-db
plan: starter
Docker
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 9000
CMD ["npm", "run", "start"]
CLI 命令
# 开发
npm run dev # 启动开发服务器
# 数据库
npx medusa db:migrate # 运行迁移
npx medusa db:sync # 同步模式
# 用户
npx medusa user -e email -p pass # 创建管理用户
# 构建
npm run build # 构建生产环境
npm run start # 启动生产服务器
清单
设置
- [ ] 配置 PostgreSQL 数据库
- [ ] 配置 Redis(可选,但推荐)
- [ ] 创建管理用户
- [ ] 配置 CORS 来源
- [ ] 设置 JWT/Cookie 密钥
自定义
- [ ] 自定义模块用于业务逻辑
- [ ] 自定义 API 路由用于前端
- [ ] 订阅者用于事件处理
- [ ] 工作流用于复杂操作
部署
- [ ] 配置环境变量
- [ ] 运行数据库迁移
- [ ] 启用 HTTPS
- [ ] 保护管理 URL
反模式
- 编辑 .medusa 文件夹 - 自动生成,将被覆盖
- 直接数据库访问 - 使用服务和模块
- 跳过复杂操作的工作流 - 工作流提供回滚
- 硬编码 URL - 使用环境变量
- 忽略 TypeScript 错误 - 框架依赖类型