16.4 使用 Express 搭建 REST API
Express 基础与项目初始化
1. 项目设置与基本结构
# 创建项目目录并初始化
mkdir express-api
cd express-api
npm init -y
# 安装依赖
npm install express body-parser cors morgan
npm install --save-dev nodemon
2. 基础 Express 应用
// src/app.js
const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const app = express();
// 中间件
app.use(cors());
app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 健康检查路由
app.get('/health', (req, res) => {
res.json({ status: 'UP' });
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
module.exports = app;
RESTful 路由设计
1. 资源路由结构
// src/routes/products.js
const express = require('express');
const router = express.Router();
const Product = require('../models/product');
// GET /products - 获取所有产品
router.get('/', async (req, res, next) => {
try {
const products = await Product.find();
res.json(products);
} catch (err) {
next(err);
}
});
// GET /products/:id - 获取单个产品
router.get('/:id', async (req, res, next) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json(product);
} catch (err) {
next(err);
}
});
// POST /products - 创建新产品
router.post('/', async (req, res, next) => {
try {
const product = new Product(req.body);
const savedProduct = await product.save();
res.status(201).json(savedProduct);
} catch (err) {
next(err);
}
});
// PUT /products/:id - 更新产品
router.put('/:id', async (req, res, next) => {
try {
const updatedProduct = await Product.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!updatedProduct) {
return res.status(404).json({ error: 'Product not found' });
}
res.json(updatedProduct);
} catch (err) {
next(err);
}
});
// DELETE /products/:id - 删除产品
router.delete('/:id', async (req, res, next) => {
try {
const deletedProduct = await Product.findByIdAndDelete(req.params.id);
if (!deletedProduct) {
return res.status(404).json({ error: 'Product not found' });
}
res.status(204).end();
} catch (err) {
next(err);
}
});
module.exports = router;
2. 路由注册
// src/app.js
const productsRouter = require('./routes/products');
const usersRouter = require('./routes/users');
// 添加路由前缀
app.use('/api/products', productsRouter);
app.use('/api/users', usersRouter);
数据验证与中间件
1. 使用 Joi 进行请求验证
// src/middlewares/validation.js
const Joi = require('joi');
const productSchema = Joi.object({
name: Joi.string().min(3).max(100).required(),
price: Joi.number().min(0).precision(2).required(),
description: Joi.string().max(500),
inStock: Joi.boolean().default(true)
});
function validateProduct(req, res, next) {
const { error } = productSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
next();
}
module.exports = { validateProduct };
2. 认证中间件
// src/middlewares/auth.js
const jwt = require('jsonwebtoken');
function authenticate(req, res, next) {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Authentication required' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
}
function authorize(role) {
return (req, res, next) => {
if (req.user.role !== role) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
module.exports = { authenticate, authorize };
数据库集成
1. Mongoose 模型定义
// src/models/product.js
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 3,
maxlength: 100
},
price: {
type: Number,
required: true,
min: 0
},
description: {
type: String,
maxlength: 500
},
inStock: {
type: Boolean,
default: true
},
createdAt: {
type: Date,
default: Date.now
}
});
// 添加索引
productSchema.index({ name: 'text', description: 'text' });
// 虚拟属性和方法
productSchema.virtual('formattedPrice').get(function() {
return `$${this.price.toFixed(2)}`;
});
// 中间件钩子
productSchema.pre('save', function(next) {
console.log(`Saving product: ${this.name}`);
next();
});
module.exports = mongoose.model('Product', productSchema);
2. 数据库连接
// src/db.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
useFindAndModify: false
});
console.log('MongoDB connected');
} catch (err) {
console.error('Database connection error:', err);
process.exit(1);
}
};
module.exports = connectDB;
高级功能实现
1. 文件上传
// src/routes/uploads.js
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const router = express.Router();
const uploadDir = path.join(__dirname, '../uploads');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadDir);
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
}
});
const upload = multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
fileFilter: (req, file, cb) => {
const filetypes = /jpeg|jpg|png|gif/;
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = filetypes.test(file.mimetype);
if (extname && mimetype) {
return cb(null, true);
}
cb(new Error('Only images are allowed'));
}
});
router.post('/', upload.single('image'), (req, res) => {
res.json({
filename: req.file.filename,
path: `/uploads/${req.file.filename}`
});
});
module.exports = router;
2. 分页与过滤
// src/routes/products.js
router.get('/', async (req, res, next) => {
try {
// 分页参数
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
// 过滤条件
const filter = {};
if (req.query.name) {
filter.name = new RegExp(req.query.name, 'i');
}
if (req.query.minPrice) {
filter.price = { $gte: parseFloat(req.query.minPrice) };
}
// 排序
const sort = {};
if (req.query.sortBy) {
const parts = req.query.sortBy.split(':');
sort[parts[0]] = parts[1] === 'desc' ? -1 : 1;
}
const [products, total] = await Promise.all([
Product.find(filter)
.skip(skip)
.limit(limit)
.sort(sort),
Product.countDocuments(filter)
]);
res.json({
data: products,
meta: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
} catch (err) {
next(err);
}
});
测试与文档
1. API 测试 (使用 Jest 和 Supertest)
// tests/products.test.js
const request = require('supertest');
const app = require('../src/app');
const Product = require('../src/models/product');
describe('Products API', () => {
beforeAll(async () => {
await Product.deleteMany();
});
describe('POST /api/products', () => {
it('should create a new product', async () => {
const response = await request(app)
.post('/api/products')
.send({
name: 'Test Product',
price: 19.99,
description: 'Test description'
})
.expect(201);
expect(response.body).toHaveProperty('_id');
expect(response.body.name).toBe('Test Product');
});
});
describe('GET /api/products', () => {
it('should retrieve all products', async () => {
await Product.create([
{ name: 'Product 1', price: 10 },
{ name: 'Product 2', price: 20 }
]);
const response = await request(app)
.get('/api/products')
.expect(200);
expect(response.body.length).toBe(2);
});
});
});
2. API 文档 (使用 Swagger)
// src/swagger.js
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'Express API',
version: '1.0.0',
description: 'API documentation for Express application'
},
servers: [
{
url: 'http://localhost:3000/api'
}
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
}
},
apis: ['./src/routes/*.js']
};
const specs = swaggerJsdoc(options);
module.exports = (app) => {
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
};
生产环境优化
1. 安全加固
// src/security.js
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const securityMiddleware = (app) => {
// 设置安全相关的HTTP头
app.use(helmet());
// 限制请求频率
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 每个IP限制100个请求
});
app.use(limiter);
// 防止XSS攻击
app.use((req, res, next) => {
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
// 防止MIME嗅探
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
});
};
module.exports = securityMiddleware;
2. 性能优化
// src/performance.js
const compression = require('compression');
const responseTime = require('response-time');
const performanceMiddleware = (app) => {
// 启用Gzip压缩
app.use(compression());
// 记录响应时间
app.use(responseTime());
// 静态文件缓存
app.use('/static', express.static('public', {
maxAge: '1y',
immutable: true
}));
};
module.exports = performanceMiddleware;
完整项目结构
express-api/
├── src/
│ ├── app.js # 应用入口
│ ├── db.js # 数据库连接
│ ├── config/ # 配置文件
│ │ └── index.js
│ ├── controllers/ # 业务逻辑
│ │ └── products.js
│ ├── models/ # 数据模型
│ │ └── product.js
│ ├── routes/ # 路由定义
│ │ ├── products.js
│ │ └── users.js
│ ├── middlewares/ # 自定义中间件
│ │ ├── auth.js
│ │ └── validation.js
│ ├── services/ # 服务层
│ │ └── productService.js
│ ├── utils/ # 工具函数
│ │ └── logger.js
│ └── swagger.js # API文档
├── tests/ # 测试文件
│ └── products.test.js
├── uploads/ # 上传文件目录
├── .env # 环境变量
├── package.json
└── README.md
部署与扩展
1. Docker 部署
# Dockerfile
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
ENV PORT=3000
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "src/app.js"]
2. 集群模式
// src/cluster.js
const cluster = require('cluster');
const os = require('os');
const app = require('./app');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
console.log(`Master ${process.pid} is running`);
// Fork workers
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork(); // 重启worker
});
} else {
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Worker ${process.pid} started on port ${PORT}`);
});
}
总结
通过 Express 构建 RESTful API 时,关键点包括:
- 路由组织:按照资源组织路由,遵循 REST 规范
- 中间件使用:合理使用中间件处理请求生命周期
- 错误处理:统一错误处理机制
- 数据验证:请求数据验证确保安全性
- 数据库集成:使用 ORM/ODM 简化数据操作
- API文档:维护良好的 API 文档
- 测试覆盖:编写全面的测试用例
- 安全加固:实施必要的安全措施
- 性能优化:考虑缓存、压缩等技术
- 可扩展性:设计可扩展的架构
Express 的灵活性和丰富的中间件生态使其成为构建 Node.js REST API 的理想选择,结合现代 JavaScript 特性和最佳实践,可以开发出高效、可维护的后端服务。
#前端开发
分享于 2025-03-25