主题模式
Express深入解析
Express 是一个基于 Node.js 平台的极简、灵活的 web 应用框架,它提供了一系列强大的特性,帮助我们创建各种 Web 和移动设备应用。在掌握了 Express 基础之后,我们需要深入了解其核心原理和高级特性,以构建更高效、更安全的应用。
1. Express核心概念
1.1 Express简介与架构
Express 是一个轻量级的 Web 应用框架,它基于 Node.js 内置的 HTTP 模块构建。Express 的设计理念是"最小化"和"灵活性",它不会强制开发者使用特定的项目结构或模式,而是提供了基础的路由和中间件功能。
javascript
// Express 基本结构
const express = require('express');
const app = express();
// 路由定义
app.get('/', (req, res) => {
res.send('Hello World!');
});
// 启动服务器
app.listen(3000, () => {
console.log('Server running on port 3000');
});Express 的核心组件包括:
- 应用程序对象 (app) - Express 应用的实例
- 请求对象 (req) - HTTP 请求对象的增强版
- 响应对象 (res) - HTTP 响应对象的增强版
- 中间件 (middleware) - 处理请求和响应的函数
- 路由 (routing) - 确定应用如何响应客户端请求
1.2 中间件机制详解
中间件是 Express 应用的核心概念,它是一个函数,可以访问请求对象 (req)、响应对象 (res) 和应用请求-响应循环中的下一个中间件函数 (next)。
javascript
// 中间件的基本结构
function middleware(req, res, next) {
// 处理请求
console.log('Middleware executed');
// 调用下一个中间件
next();
}
// 使用中间件
app.use(middleware);
// 有条件地跳过中间件
function conditionalMiddleware(req, res, next) {
if (req.headers['x-skip-middleware']) {
// 跳过中间件
next();
} else {
// 处理请求
console.log('Conditional middleware executed');
next();
}
}中间件的类型:
- 应用级中间件 - 使用 app.use() 或 app.METHOD() 绑定到 app 实例
- 路由器级中间件 - 使用 router.use() 或 router.METHOD() 绑定到 router 实例
- 错误处理中间件 - 接收四个参数 (err, req, res, next)
- 内置中间件 - Express 内置的中间件函数
- 第三方中间件 - 通过 npm 安装的中间件
1.3 路由系统深入
Express 的路由系统允许我们定义应用如何响应客户端的不同 HTTP 请求。
javascript
// 基本路由
app.get('/', (req, res) => {
res.send('GET request to homepage');
});
app.post('/', (req, res) => {
res.send('POST request to homepage');
});
// 路由参数
app.get('/users/:userId/books/:bookId', (req, res) => {
res.send(req.params);
// { "userId": "34", "bookId": "8989" }
});
// 查询字符串
app.get('/search', (req, res) => {
res.send(req.query);
// GET /search?q=tobi+ferret&order=desc
// { "q": "tobi ferret", "order": "desc" }
});
// 正则表达式路由
app.get(/.*fly$/, (req, res) => {
res.send('/.*fly$/');
});1.4 请求与响应对象
Express 扩展了 Node.js 的原生请求和响应对象,提供了更多的实用方法。
javascript
// 请求对象常用属性和方法
app.get('/user/:id', (req, res) => {
console.log(req.params); // 路由参数
console.log(req.query); // 查询字符串
console.log(req.body); // 请求体(需要 body-parser)
console.log(req.headers); // 请求头
console.log(req.method); // HTTP 方法
console.log(req.url); // 请求 URL
});
// 响应对象常用方法
app.get('/response-examples', (req, res) => {
// 发送文本响应
res.send('Hello World');
// 发送 JSON 响应
res.json({ message: 'Hello World' });
// 发送文件
res.sendFile('/path/to/file.txt');
// 设置状态码
res.status(404).send('Not Found');
// 设置响应头
res.set('Content-Type', 'text/plain');
// 重定向
res.redirect('/login');
// 下载文件
res.download('/path/to/file.pdf');
});2. 中间件深度解析
2.1 内置中间件
Express 提供了一些内置中间件函数:
javascript
// express.static - 提供静态文件服务
app.use(express.static('public'));
// express.json - 解析 JSON 请求体
app.use(express.json());
// express.urlencoded - 解析 URL-encoded 请求体
app.use(express.urlencoded({ extended: true }));
// express.raw - 解析原始 Buffer 请求体
app.use(express.raw());
// express.text - 解析文本请求体
app.use(express.text());2.2 第三方中间件
常用的第三方中间件:
javascript
// body-parser - 解析请求体(Express 4.16.0 之前必需)
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// cors - 处理跨域资源共享
const cors = require('cors');
app.use(cors());
// morgan - HTTP 请求日志记录
const logger = require('morgan');
app.use(logger('combined'));
// helmet - 安全头设置
const helmet = require('helmet');
app.use(helmet());
// compression - 响应压缩
const compression = require('compression');
app.use(compression());2.3 自定义中间件开发
创建可重用的自定义中间件:
javascript
// 日志中间件
function logger(req, res, next) {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
next();
}
// 认证中间件
function requireAuth(req, res, next) {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'Authorization header required' });
}
try {
// 验证 token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
}
// 错误处理中间件
function errorHandler(err, req, res, next) {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
}
// 注册中间件
app.use(logger);
app.use('/api', requireAuth);
app.use(errorHandler);2.4 错误处理中间件
错误处理中间件必须有四个参数:
javascript
// 错误处理中间件
function errorHandler(err, req, res, next) {
// 错误日志记录
console.error(err.stack);
// 根据错误类型返回不同的响应
if (err instanceof ValidationError) {
return res.status(400).json({ error: err.message });
}
if (err instanceof AuthenticationError) {
return res.status(401).json({ error: err.message });
}
// 默认错误响应
res.status(500).json({ error: 'Internal Server Error' });
}
// 异步错误处理
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
// 使用异步错误处理
app.get('/async-route', asyncHandler(async (req, res) => {
const data = await someAsyncOperation();
res.json(data);
}));3. 路由与控制器
3.1 路由参数与查询字符串
深入理解路由参数和查询字符串的处理:
javascript
// 路由参数验证
app.get('/users/:userId(\\d+)', (req, res) => {
// 只匹配数字类型的 userId
res.send(`User ID: ${req.params.userId}`);
});
// 可选参数
app.get('/posts/:postId?', (req, res) => {
if (req.params.postId) {
res.send(`Post ID: ${req.params.postId}`);
} else {
res.send('All posts');
}
});
// 通配符参数
app.get('/files/*', (req, res) => {
res.send(`File path: ${req.params[0]}`);
});
// 查询字符串数组
app.get('/search', (req, res) => {
// GET /search?tags=nodejs&tags=express&tags=api
const tags = Array.isArray(req.query.tags) ? req.query.tags : [req.query.tags];
res.json({ tags });
});3.2 路由组与模块化
使用 Router 对象组织路由:
javascript
// users.js - 用户路由模块
const express = require('express');
const router = express.Router();
// 中间件仅应用于这个路由器
router.use((req, res, next) => {
console.log('Users router middleware');
next();
});
// 路由定义
router.get('/', (req, res) => {
res.json({ message: 'Get all users' });
});
router.get('/:id', (req, res) => {
res.json({ message: `Get user ${req.params.id}` });
});
router.post('/', (req, res) => {
res.json({ message: 'Create user', data: req.body });
});
module.exports = router;
// app.js - 主应用文件
const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);3.3 控制器设计模式
分离路由和业务逻辑:
javascript
// controllers/userController.js
class UserController {
static async getAllUsers(req, res, next) {
try {
const users = await User.findAll();
res.json(users);
} catch (err) {
next(err);
}
}
static async getUserById(req, res, next) {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (err) {
next(err);
}
}
static async createUser(req, res, next) {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (err) {
next(err);
}
}
}
module.exports = UserController;
// routes/users.js
const express = require('express');
const router = express.Router();
const UserController = require('../controllers/userController');
router.get('/', UserController.getAllUsers);
router.get('/:id', UserController.getUserById);
router.post('/', UserController.createUser);
module.exports = router;3.4 RESTful API设计
遵循 RESTful 设计原则:
javascript
// RESTful 路由设计示例
app.get('/api/posts', getAllPosts); // 获取所有文章
app.post('/api/posts', createPost); // 创建文章
app.get('/api/posts/:id', getPostById); // 获取特定文章
app.put('/api/posts/:id', updatePost); // 更新文章
app.delete('/api/posts/:id', deletePost); // 删除文章
// 嵌套路由
app.get('/api/posts/:postId/comments', getCommentsByPost);
app.post('/api/posts/:postId/comments', createComment);
app.put('/api/comments/:id', updateComment);
app.delete('/api/comments/:id', deleteComment);
// 状态码规范
// 200 - 成功
// 201 - 创建成功
// 400 - 请求错误
// 401 - 未认证
// 403 - 无权限
// 404 - 未找到
// 500 - 服务器错误4. 数据验证与安全
4.1 输入验证
使用 Joi 进行数据验证:
javascript
const Joi = require('joi');
// 定义验证模式
const userSchema = Joi.object({
name: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')).required(),
age: Joi.number().integer().min(0).max(120),
roles: Joi.array().items(Joi.string()).unique()
});
// 验证中间件
function validateUser(req, res, next) {
const { error, value } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
req.validatedBody = value;
next();
}
// 使用验证中间件
app.post('/api/users', validateUser, (req, res) => {
// req.validatedBody 包含已验证的数据
res.json({ message: 'User created', data: req.validatedBody });
});4.2 身份认证与授权
实现 JWT 认证:
javascript
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// 登录路由
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
// 查找用户
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// 验证密码
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// 生成 JWT
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
res.json({ token, user: { id: user.id, email: user.email } });
});
// 认证中间件
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
req.user = user;
next();
});
}
// 授权中间件
function authorizeRole(requiredRole) {
return (req, res, next) => {
if (!req.user.roles.includes(requiredRole)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// 使用认证和授权
app.get('/api/profile', authenticateToken, (req, res) => {
res.json({ user: req.user });
});
app.delete('/api/users/:id', authenticateToken, authorizeRole('admin'), (req, res) => {
res.json({ message: 'User deleted' });
});4.3 CSRF防护
实施 CSRF 防护:
javascript
const csrf = require('csurf');
// CSRF 保护中间件
const csrfProtection = csrf({ cookie: true });
// 为需要保护的路由添加 CSRF 保护
app.use('/api/admin', csrfProtection);
// 提供 CSRF token
app.get('/api/csrf-token', csrfProtection, (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
// 在表单中使用 CSRF token
/*
<form method="POST" action="/api/admin/users">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="text" name="username">
<button type="submit">Create User</button>
</form>
*/4.4 安全头设置
使用 Helmet 设置安全头:
javascript
const helmet = require('helmet');
app.use(helmet());
// 自定义安全头
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
dnsPrefetchControl: false,
expectCt: {
enforce: true,
maxAge: 30,
},
}));
// 速率限制
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100, // 限制每个 IP 15 分钟内最多 100 个请求
message: 'Too many requests from this IP, please try again later.'
});
app.use(limiter);5. 数据库集成
5.1 MongoDB与Mongoose
使用 Mongoose 操作 MongoDB:
javascript
const mongoose = require('mongoose');
// 连接数据库
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
});
// 定义 Schema
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
isActive: { type: Boolean, default: true }
});
// 添加实例方法
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
// 添加静态方法
userSchema.statics.findByEmail = function(email) {
return this.findOne({ email });
};
// 创建 Model
const User = mongoose.model('User', userSchema);
// CRUD 操作
// 创建
app.post('/api/users', async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// 查询
app.get('/api/users', async (req, res) => {
try {
const users = await User.find({ isActive: true });
res.json(users);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 更新
app.put('/api/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// 删除
app.delete('/api/users/:id', async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json({ message: 'User deleted' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});5.2 PostgreSQL与Sequelize
使用 Sequelize 操作 PostgreSQL:
javascript
const { Sequelize, DataTypes } = require('sequelize');
// 创建连接
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'postgres'
});
// 定义模型
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
}
}, {
timestamps: true,
tableName: 'users'
});
// 同步模型到数据库
sequelize.sync();
// CRUD 操作
// 创建
app.post('/api/users', async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// 查询
app.get('/api/users', async (req, res) => {
try {
const { page = 1, limit = 10 } = req.query;
const offset = (page - 1) * limit;
const { rows, count } = await User.findAndCountAll({
limit: parseInt(limit),
offset: parseInt(offset),
order: [['createdAt', 'DESC']]
});
res.json({
users: rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: count,
pages: Math.ceil(count / limit)
}
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 关联查询
const Post = sequelize.define('Post', {
title: DataTypes.STRING,
content: DataTypes.TEXT
});
User.hasMany(Post);
Post.belongsTo(User);
app.get('/api/users/:id/posts', async (req, res) => {
try {
const user = await User.findByPk(req.params.id, {
include: [{
model: Post,
attributes: ['id', 'title', 'createdAt']
}]
});
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user.Posts);
} catch (err) {
res.status(500).json({ error: err.message });
}
});5.3 数据库连接池
配置数据库连接池:
javascript
// MongoDB 连接池配置
mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true,
maxPoolSize: 10, // 连接池最大连接数
serverSelectionTimeoutMS: 5000, // 服务器选择超时时间
socketTimeoutMS: 45000, // Socket 超时时间
});
// PostgreSQL 连接池配置
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'postgres',
pool: {
max: 20, // 最大连接数
min: 5, // 最小连接数
acquire: 30000, // 获取连接的超时时间
idle: 10000 // 连接空闲时间
}
});5.4 事务处理
在数据库操作中使用事务:
javascript
// MongoDB 事务
app.post('/api/transfer', async (req, res) => {
const session = await mongoose.startSession();
session.startTransaction();
try {
const { fromAccountId, toAccountId, amount } = req.body;
// 从账户扣款
const fromAccount = await Account.findByIdAndUpdate(
fromAccountId,
{ $inc: { balance: -amount } },
{ session, new: true }
);
if (fromAccount.balance < 0) {
throw new Error('Insufficient funds');
}
// 向账户存款
await Account.findByIdAndUpdate(
toAccountId,
{ $inc: { balance: amount } },
{ session }
);
// 提交事务
await session.commitTransaction();
session.endSession();
res.json({ message: 'Transfer successful' });
} catch (err) {
// 回滚事务
await session.abortTransaction();
session.endSession();
res.status(400).json({ error: err.message });
}
});
// PostgreSQL 事务
app.post('/api/orders', async (req, res) => {
const transaction = await sequelize.transaction();
try {
// 创建订单
const order = await Order.create(req.body, { transaction });
// 更新库存
await Product.decrement('stock', {
by: req.body.quantity,
where: { id: req.body.productId },
transaction
});
// 提交事务
await transaction.commit();
res.status(201).json(order);
} catch (err) {
// 回滚事务
await transaction.rollback();
res.status(400).json({ error: err.message });
}
});6. 性能优化
6.1 缓存策略
实现多层缓存:
javascript
const redis = require('redis');
const client = redis.createClient();
// Redis 缓存中间件
function cacheMiddleware(duration = 300) {
return async (req, res, next) => {
const key = '__express__' + req.originalUrl || req.url;
try {
const cached = await client.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
// 重写 res.send 方法来缓存响应
res.sendResponse = res.send;
res.send = (body) => {
client.setex(key, duration, body);
res.sendResponse(body);
};
next();
} catch (err) {
next();
}
};
}
// 使用缓存
app.get('/api/users', cacheMiddleware(600), async (req, res) => {
const users = await User.find({});
res.json(users);
});
// 内存缓存
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 });
function memoryCacheMiddleware(req, res, next) {
const key = req.originalUrl || req.url;
const cached = cache.get(key);
if (cached) {
return res.json(cached);
}
res.sendResponse = res.send;
res.send = (body) => {
cache.set(key, JSON.parse(body));
res.sendResponse(body);
};
next();
}6.2 数据库查询优化
优化数据库查询:
javascript
// 索引优化
// 为经常查询的字段创建索引
User.index({ email: 1 });
User.index({ createdAt: -1 });
// 查询优化
app.get('/api/users', async (req, res) => {
try {
// 只选择需要的字段
const users = await User.find({}, { name: 1, email: 1, _id: 1 });
// 分页查询
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const users = await User.find({})
.select('name email')
.skip(skip)
.limit(limit)
.sort({ createdAt: -1 });
res.json(users);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 聚合管道优化
app.get('/api/users/stats', async (req, res) => {
try {
const stats = await User.aggregate([
{
$group: {
_id: '$status',
count: { $sum: 1 }
}
},
{
$project: {
status: '$_id',
count: 1,
_id: 0
}
}
]);
res.json(stats);
} catch (err) {
res.status(500).json({ error: err.message });
}
});6.3 压缩与静态资源处理
优化静态资源:
javascript
const compression = require('compression');
const expressStaticGzip = require('express-static-gzip');
// 响应压缩
app.use(compression());
// 静态资源压缩
app.use('/static', expressStaticGzip('public', {
enableBrotli: true,
orderPreference: ['br', 'gz'],
serveStatic: {
maxAge: '1y'
}
}));
// ETag 设置
app.set('etag', 'strong'); // 或 'weak'
// 条件请求处理
app.use((req, res, next) => {
res.set('Cache-Control', 'public, max-age=300');
next();
});6.4 负载均衡
集群模式部署:
javascript
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
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 {
// Workers can share any TCP connection
// In this case it is an HTTP server
const app = require('./app');
app.listen(3000, () => {
console.log(`Worker ${process.pid} started`);
});
}7. 错误处理与日志
7.1 统一错误处理
建立统一的错误处理机制:
javascript
// 自定义错误类
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
// 全局错误处理中间件
function globalErrorHandler(err, req, res, next) {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
// 开发环境显示详细错误信息
if (process.env.NODE_ENV === 'development') {
res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
} else {
// 生产环境隐藏敏感信息
if (err.isOperational) {
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
} else {
// 编程错误或其他未知错误
console.error('ERROR ', err);
res.status(500).json({
status: 'error',
message: 'Something went very wrong!'
});
}
}
}
// 异步错误处理包装器
function catchAsync(fn) {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
}
// 使用示例
app.get('/api/users/:id', catchAsync(async (req, res, next) => {
const user = await User.findById(req.params.id);
if (!user) {
return next(new AppError('User not found', 404));
}
res.json(user);
}));7.2 日志记录
使用 Winston 记录日志:
javascript
const winston = require('winston');
const expressWinston = require('express-winston');
// 创建 logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' })
]
});
// 在生产环境中也输出到控制台
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
// 请求日志中间件
app.use(expressWinston.logger({
transports: [
new winston.transports.File({ filename: 'logs/request.log' })
],
format: winston.format.combine(
winston.format.json()
),
meta: true,
msg: "HTTP {{req.method}} {{req.url}}",
expressFormat: true,
colorize: false,
ignoreRoute: function (req, res) { return false; }
}));
// 使用 logger
app.get('/api/test', (req, res) => {
logger.info('Test endpoint accessed');
logger.warn('This is a warning');
res.json({ message: 'Success' });
});7.3 异常监控
集成异常监控服务:
javascript
// Sentry 集成
const Sentry = require('@sentry/node');
const Tracing = require('@sentry/tracing');
Sentry.init({
dsn: "YOUR_SENTRY_DSN",
integrations: [
new Sentry.Integrations.Http({ tracing: true }),
new Tracing.Integrations.Express({ app }),
],
tracesSampleRate: 1.0,
});
app.use(Sentry.Handlers.requestHandler());
app.use(Sentry.Handlers.tracingHandler());
// 在路由之后添加错误处理
app.use(Sentry.Handlers.errorHandler());
// 自定义异常上报
function reportError(err, req) {
Sentry.captureException(err, {
user: req.user,
extra: {
url: req.url,
method: req.method,
headers: req.headers
}
});
}7.4 调试技巧
使用调试工具:
javascript
// 使用 debug 模块
const debug = require('debug')('app:startup');
const dbDebug = require('debug')('app:db');
debug('Starting application...');
dbDebug('Connecting to database...');
// 环境变量控制调试输出
// DEBUG=app:* npm start
// DEBUG=app:db npm start
// DEBUG=* npm start
// 性能监控
console.time('Database Query');
const users = await User.find({});
console.timeEnd('Database Query');
// 内存使用监控
app.use((req, res, next) => {
const used = process.memoryUsage();
console.log('Memory usage:', used);
next();
});8. 测试与部署
8.1 单元测试与集成测试
使用 Jest 进行测试:
javascript
// __tests__/userController.test.js
const request = require('supertest');
const app = require('../app');
const User = require('../models/User');
describe('User API', () => {
beforeEach(async () => {
await User.deleteMany({});
});
describe('GET /api/users', () => {
it('should get all users', async () => {
await User.create({ name: 'John', email: 'john@example.com' });
const res = await request(app)
.get('/api/users')
.expect(200);
expect(res.body).toHaveLength(1);
expect(res.body[0].name).toBe('John');
});
});
describe('POST /api/users', () => {
it('should create a new user', async () => {
const userData = {
name: 'Jane',
email: 'jane@example.com',
password: 'password123'
};
const res = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(res.body.name).toBe('Jane');
expect(res.body.email).toBe('jane@example.com');
const user = await User.findOne({ email: 'jane@example.com' });
expect(user).toBeTruthy();
});
it('should return 400 for invalid data', async () => {
const invalidData = {
name: 'J',
email: 'invalid-email'
};
await request(app)
.post('/api/users')
.send(invalidData)
.expect(400);
});
});
});
// 单元测试示例
// __tests__/utils.test.js
const { validateEmail } = require('../utils/validation');
describe('Validation Utils', () => {
describe('validateEmail', () => {
it('should return true for valid emails', () => {
expect(validateEmail('test@example.com')).toBe(true);
expect(validateEmail('user.name@domain.co.uk')).toBe(true);
});
it('should return false for invalid emails', () => {
expect(validateEmail('invalid-email')).toBe(false);
expect(validateEmail('')).toBe(false);
expect(validateEmail(null)).toBe(false);
});
});
});8.2 API测试
使用 Postman 或 Newman 进行 API 测试:
javascript
// test/api-tests.js
const newman = require('newman');
newman.run({
collection: require('./api-tests.postman_collection.json'),
environment: require('./local.postman_environment.json'),
reporters: ['cli', 'json'],
reporter: {
json: {
export: './reports/api-test-results.json'
}
}
}, function (err) {
if (err) {
throw err;
}
console.log('API tests completed!');
});8.3 Docker容器化部署
创建 Docker 配置:
dockerfile
# Dockerfile
FROM node:16-alpine
WORKDIR /app
# 复制 package.json 和 package-lock.json
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制应用代码
COPY . .
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]yaml
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=mongodb://mongo:27017/myapp
depends_on:
- mongo
- redis
volumes:
- ./logs:/app/logs
mongo:
image: mongo:4.4
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
mongo_data:
redis_data:8.4 性能监控
集成性能监控:
javascript
// 使用 Prometheus 监控指标
const client = require('prom-client');
// 创建默认指标
const collectDefaultMetrics = client.collectDefaultMetrics;
collectDefaultMetrics({ timeout: 5000 });
// 自定义指标
const httpRequestDurationMicroseconds = new client.Histogram({
name: 'http_request_duration_ms',
help: 'Duration of HTTP requests in ms',
labelNames: ['method', 'route', 'code'],
buckets: [0.10, 5, 15, 50, 100, 200, 300, 400, 500]
});
// 中间件记录指标
app.use((req, res, next) => {
const end = httpRequestDurationMicroseconds.startTimer();
res.on('finish', () => {
const labels = {
method: req.method,
route: req.route ? req.route.path : req.path,
code: res.statusCode
};
end(labels);
});
next();
});
// 暴露指标端点
app.get('/metrics', async (req, res) => {
res.set('Content-Type', client.register.contentType);
res.end(await client.register.metrics());
});通过对 Express 核心概念和高级特性的深入理解,我们可以更好地利用 Express 构建高性能、可维护的 Web 应用程序。在实际开发中,我们应该根据项目需求选择合适的技术方案,并持续关注 Node.js 和 Express 生态的发展。