后端API开发实战
今天用Claude Code构建一个完整的电商后端API系统,涵盖RESTful API、GraphQL服务、中间件开发、错误处理、API文档生成等核心内容。
项目概述
项目目标
构建电商API后端系统,包括:
- RESTful API: 完整的商品、订单、用户管理接口
- GraphQL服务: 灵活的数据查询接口
- 中间件系统: 认证、授权、日志、限流等
- 错误处理: 统一的错误处理机制
- API文档: 自动生成的Swagger文档
- 数据验证: 请求参数验证和数据清理
- 单元测试: 完整的 测试覆盖
技术栈
- 框架: Node.js + Express.js
- 数据库: PostgreSQL + MongoDB(混合使用)
- ORM: Prisma(用于PostgreSQL)
- 认证: JWT + Passport.js
- 验证: Joi + express-validator
- 文档: Swagger/OpenAPI
- GraphQL: Apollo Server
- 测试: Jest + Supertest
- 日志: Winston + Morgan
- 限流: express-rate-limit
- 缓存: Redis
项目结构预览
ecommerce-api/
├── src/
│ ├── config/ # 配置文件
│ │ ├── database.js
│ │ ├── redis.js
│ │ └── passport.js
│ ├── controllers/ # 控制器
│ │ ├── product.controller.js
│ │ ├── order.controller.js
│ │ └── user.controller.js
│ ├── middleware/ # 中间件
│ │ ├── auth.middleware.js
│ │ ├── validate.middleware.js
│ │ ├── error.middleware.js
│ │ ├── logger.middleware.js
│ │ └── rateLimit.middleware.js
│ ├── models/ # Prisma模型
│ │ └── schema.prisma
│ ├── routes/ # RESTful路由
│ │ ├── index.js
│ │ ├── products.js
│ │ ├── orders.js
│ │ └── users.js
│ ├── graphql/ # GraphQL相关
│ │ ├── schema.js
│ │ ├── resolvers.js
│ │ └── typeDefs.js
│ ├── services/ # 业务逻辑层
│ │ ├── product.service.js
│ │ ├── order.service.js
│ │ └── user.service.js
│ ├── utils/ # 工具函数
│ │ ├── validators.js
│ │ ├── helpers.js
│ │ └── constants.js
│ ├── validators/ # 请求验证模式
│ │ ├── product.validator.js
│ │ ├── order.validator.js
│ │ └── user.validator.js
│ ├── tests/ # 测试文件
│ │ ├── unit/
│ │ └── integration/
│ ├── app.js # Express应用配置
│ └── server.js # 服务器入口
├── prisma/ # Prisma配置
│ └── schema.prisma
├── docs/ # API文档
├── .env.example
├── .eslintrc.js
├── .prettierrc
├── package.json
└── README.md
第一阶段:项目初始化和基础架构
第一轮对话
启动Claude Code:
cd ~/projects
mkdir ecommerce-api
cd ecommerce-api
claude
我要创建一个电商后端API系统,包含:
1. RESTful API设计(商品、订单、用户管理)
2. GraphQL服务(灵活查询)
3. JWT认证和授权
4. 中间件系统(日志、限流、CORS)
5. 统一错误处理
6. 请求参数验证
7. Swagger API文档自动生成
8. PostgreSQL数据库(使用Prisma ORM)
9. Redis缓存
10. 单元测试和集成测试
技术栈:
- Node.js + Express
- Prisma + PostgreSQL
- Redis
- JWT + Passport
- Swagger
- Apollo Server(GraphQL)
请帮我创建完整的项目结构和基础配置。
Claude Code会立即分析需求并开始构建:
🤖 Claude: 我将帮你创建一个专业级的电商API后端系统。
让我先创建项目的基础结构和配置文件...
📁 Creating project structure...
✓ Created src/ directory with subdirectories
✓ Created prisma/ directory
✓ Created tests/ directory
✓ Created .gitignore
✓ Created .env.example
✓ Created package.json with all dependencies
✓ Created ESLint and Prettier configurations
✓ Created README.md with project documentation
Now let me set up the configuration files...
项目配置文件
package.json:
{
"name": "ecommerce-api",
"version": "1.0.0",
"description": "Professional E-commerce API Backend",
"main": "src/server.js",
"scripts": {
"start": "node src/server.js",
"dev": "nodemon src/server.js",
"test": "jest --coverage",
"test:watch": "jest --watch",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
"prisma:studio": "prisma studio",
"lint": "eslint src/**/*.js",
"lint:fix": "eslint src/**/*.js --fix",
"format": "prettier --write \"src/**/*.js\""
},
"keywords": [
"ecommerce",
"api",
"rest",
"graphql",
"express",
"prisma"
],
"author": "",
"license": "MIT",
"dependencies": {
"@prisma/client": "^5.7.0",
"apollo-server-express": "^3.12.1",
"bcryptjs": "^2.4.3",
"compression": "^1.7.4",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"express-validator": "^7.0.1",
"graphql": "^16.8.1",
"helmet": "^7.1.0",
"ioredis": "^5.3.2",
"joi": "^17.11.0",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"redis": "^4.6.11",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0",
"winston": "^3.11.0"
},
"devDependencies": {
"eslint": "^8.55.0",
"jest": "^29.7.0",
"nodemon": "^3.0.2",
"prettier": "^3.1.1",
"prisma": "^5.7.0",
"supertest": "^6.3.3"
}
}
.env.example:
# Server Configuration
NODE_ENV=development
PORT=3000
API_VERSION=v1
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/ecommerce?schema=public"
# Redis
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
# JWT
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
JWT_EXPIRE=7d
JWT_REFRESH_SECRET=your-refresh-token-secret
JWT_REFRESH_EXPIRE=30d
# CORS
CORS_ORIGIN=http://localhost:3001
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# Logging
LOG_LEVEL=info
LOG_FILE=logs/app.log
# Pagination
DEFAULT_PAGE=1
DEFAULT_LIMIT=20
MAX_LIMIT=100
.gitignore:
# Dependencies
node_modules/
# Environment variables
.env
.env.local
.env.*.local
# Logs
logs/
*.log
npm-debug.log*
# Prisma
prisma/migrations/
# Testing
coverage/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Build
dist/
build/
Prisma数据库模型
prisma/schema.prisma:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
username String @unique
password String
role Role @default(CUSTOMER)
profile UserProfile?
addresses Address[]
orders Order[]
reviews Review[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
model UserProfile {
id String @id @default(uuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
firstName String?
lastName String?
phone String?
avatar String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("user_profiles")
}
model Address {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
fullName String
phone String
province String
city String
district String
address String
postalCode String?
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("addresses")
}
model Category {
id String @id @default(uuid())
name String @unique
slug String @unique
image String?
parentId String?
parent Category? @relation("CategoryToCategory", fields: [parentId], references: [id], onDelete: SetNull)
children Category[] @relation("CategoryToCategory")
products Product[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("categories")
}
model Product {
id String @id @default(uuid())
name String
slug String @unique
description String?
price Decimal @db.Decimal(10, 2)
comparePrice Decimal? @db.Decimal(10, 2)
sku String @unique
stock Int @default(0)
images String[]
categoryId String
category Category @relation(fields: [categoryId], references: [id])
brand String?
tags String[]
attributes Json?
isActive Boolean @default(true)
reviews Review[]
orderItems OrderItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("products")
}
model Order {
id String @id @default(uuid())
orderNumber String @unique
userId String
user User @relation(fields: [userId], references: [id])
items OrderItem[]
status OrderStatus @default(PENDING)
totalAmount Decimal @db.Decimal(10, 2)
discount Decimal @default(0) @db.Decimal(10, 2)
finalAmount Decimal @db.Decimal(10, 2)
shippingAddress Json
paymentMethod String
paymentStatus PaymentStatus @default(PENDING)
notes String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("orders")
}
model OrderItem {
id String @id @default(uuid())
orderId String
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
productId String
product Product @relation(fields: [productId], references: [id])
quantity Int
price Decimal @db.Decimal(10, 2)
totalAmount Decimal @db.Decimal(10, 2)
@@map("order_items")
}
model Review {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
productId String
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
rating Int
comment String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("reviews")
}
enum Role {
ADMIN
CUSTOMER
}
enum OrderStatus {
PENDING
CONFIRMED
PROCESSING
SHIPPED
DELIVERED
CANCELLED
REFUNDED
}
enum PaymentStatus {
PENDING
COMPLETED
FAILED
REFUNDED
}
核心配置文件
src/config/database.js:
const { PrismaClient } = require('@prisma/client');
const logger = require('../utils/logger');
const prisma = new PrismaClient({
log: [
{ level: 'query', emit: 'event' },
{ level: 'error', emit: 'stdout' },
{ level: 'warn', emit: 'stdout' },
],
});
// 监听查询事件(开发环境)
if (process.env.NODE_ENV === 'development') {
prisma.$on('query', (e) => {
logger.debug(`Query: ${e.query}`);
logger.debug(`Duration: ${e.duration}ms`);
});
}
// 测试数据库连接
const testConnection = async () => {
try {
await prisma.$connect();
logger.info('✓ PostgreSQL connected successfully');
} catch (error) {
logger.error('PostgreSQL connection error:', error);
process.exit(1);
}
};
// 优雅关闭
const gracefulShutdown = async () => {
await prisma.$disconnect();
logger.info('PostgreSQL connection closed');
};
process.on('SIGINT', gracefulShutdown);
process.on('SIGTERM', gracefulShutdown);
module.exports = { prisma, testConnection };
src/config/redis.js:
const Redis = require('ioredis');
const logger = require('../utils/logger');
const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD || undefined,
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
maxRetriesPerRequest: 3,
});
redis.on('connect', () => {
logger.info('✓ Redis connected successfully');
});
redis.on('error', (error) => {
logger.error('Redis connection error:', error);
});
// 测试连接
const testConnection = async () => {
try {
await redis.ping();
logger.info('✓ Redis ping successful');
} catch (error) {
logger.error('Redis ping failed:', error);
}
};
// 缓存辅助函数
const cache = {
// 获取缓存
get: async (key) => {
try {
const data = await redis.get(key);
return data ? JSON.parse(data) : null;
} catch (error) {
logger.error(`Cache get error for key ${key}:`, error);
return null;
}
},
// 设置缓存
set: async (key, value, ttl = 3600) => {
try {
await redis.setex(key, ttl, JSON.stringify(value));
return true;
} catch (error) {
logger.error(`Cache set error for key ${key}:`, error);
return false;
}
},
// 删除缓存
del: async (key) => {
try {
await redis.del(key);
return true;
} catch (error) {
logger.error(`Cache delete error for key ${key}:`, error);
return false;
}
},
// 删除匹配模式的所有缓存
delPattern: async (pattern) => {
try {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
return keys.length;
} catch (error) {
logger.error(`Cache delete pattern error for ${pattern}:`, error);
return 0;
}
},
};
module.exports = { redis, testConnection, cache };
src/utils/logger.js:
const winston = require('winston');
const path = require('path');
// 自定义格式
const customFormat = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
);
// 控制台格式(开发环境)
const consoleFormat = winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf(({ timestamp, level, message, ...metadata }) => {
let msg = `${timestamp} [${level}]: ${message}`;
if (Object.keys(metadata).length > 0) {
msg += ` ${JSON.stringify(metadata)}`;
}
return msg;
})
);
// 创建logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: customFormat,
defaultMeta: { service: 'ecommerce-api' },
transports: [
// 错误日志文件
new winston.transports.File({
filename: path.join(process.cwd(), 'logs', 'error.log'),
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5,
}),
// 综合日志文件
new winston.transports.File({
filename: path.join(process.cwd(), 'logs', 'combined.log'),
maxsize: 5242880,
maxFiles: 5,
}),
],
});
// 开发环境添加控制台输出
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: consoleFormat,
}));
}
module.exports = logger;
Express应用配置
src/app.js:
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const morgan = require('morgan');
const swaggerUi = require('swagger-ui-express');
const swaggerJsdoc = require('swagger-jsdoc');
const { graphqlHTTP } = require('express-graphql');
const logger = require('./utils/logger');
const { testConnection } = require('./config/database');
const { testConnection: testRedisConnection } = require('./config/redis');
// 导入路由
const routes = require('./routes');
// Swagger配置
const swaggerOptions = {
definition: {
openapi: '3.0.0',
info: {
title: 'E-commerce API',
version: '1.0.0',
description: '专业的电商后端API文档',
contact: {
name: 'API Support',
email: 'support@ecommerce.com',
},
},
servers: [
{
url: `http://localhost:${process.env.PORT || 3000}`,
description: '开发服务器',
},
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
},
security: [
{
bearerAuth: [],
},
],
},
apis: ['./src/routes/*.js', './src/controllers/*.js'],
};
const swaggerSpec = swaggerJsdoc(swaggerOptions);
// 创建Express应用
const app = express();
// 安全中间件
app.use(helmet());
// CORS配置
app.use(cors({
origin: process.env.CORS_ORIGIN || 'http://localhost:3001',
credentials: true,
}));
// 压缩响应
app.use(compression());
// 请求解析
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// HTTP请求日志
if (process.env.NODE_ENV !== 'test') {
app.use(morgan('combined', {
stream: {
write: (message) => logger.info(message.trim()),
},
}));
}
// 健康检查
app.get('/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
environment: process.env.NODE_ENV,
});
});
// API文档
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
customCss: '.swagger-ui .topbar { display: none }',
customSiteTitle: 'E-commerce API Docs',
}));
// API路由
app.use('/api', routes);
// GraphQL路由(稍后实现)
// app.use('/graphql', graphqlHTTP({ ... }));
// 404处理
app.use((req, res) => {
res.status(404).json({
success: false,
message: `Route ${req.originalUrl} not found`,
});
});
// 初始化数据库连接
const initializeDatabase = async () => {
await testConnection();
await testRedisConnection();
};
module.exports = { app, initializeDatabase };
src/server.js:
require('dotenv').config();
const { app, initializeDatabase } = require('./app');
const logger = require('./utils/logger');
const PORT = process.env.PORT || 3000;
const startServer = async () => {
try {
// 初始化数据库连接
await initializeDatabase();
// 启动服务器
const server = app.listen(PORT, () => {
logger.info(`
╔════════════════════════════════════════════╗
║ ║
║ 🚀 E-commerce API Server Started ║
║ ║
║ Environment: ${process.env.NODE_ENV || 'development'} ║
║ Port: ${PORT} ║
║ API: http://localhost:${PORT}/api ║
║ Docs: http://localhost:${PORT}/api-docs ║
║ ║
╚════ ════════════════════════════════════════╝
`);
});
// 优雅关闭
const gracefulShutdown = (signal) => {
logger.info(`${signal} received. Closing server gracefully...`);
server.close(() => {
logger.info('Server closed');
process.exit(0);
});
// 强制关闭超时
setTimeout(() => {
logger.error('Forced shutdown after timeout');
process.exit(1);
}, 10000);
};
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
} catch (error) {
logger.error('Failed to start server:', error);
process.exit(1);
}
};
startServer();
第二阶段:RESTful API设计
创建RESTful路由
创建RESTful API路由:
1. 产品路由:
- GET /api/products (列表,支持分页、过滤、排序)
- GET /api/products/:id (详情)
- POST /api/products (创建,需要管理员权限)
- PUT /api/products/:id (更新,需要管理员权限)
- DELETE /api/products/:id (删除,需要管理员权限)
2. 订单路由:
- GET /api/orders (我的订单列表)
- GET /api/orders/:id (订单详情)
- POST /api/orders (创建订单)
- PUT /api/orders/:id (更新订单状态,管理员)
3. 用户路由:
- POST /api/users/register (注册)
- POST /api/users/login (登录)
- GET /api/users/profile (获取个人信息)
- PUT /api/users/profile (更新个人信息)
请创建完整的路由文件,包含Swagger文档注释。