常用中间件 #

一、请求解析中间件 #

1.1 express.json() #

解析JSON格式的请求体:

javascript
app.use(express.json());

app.post('/data', (req, res) => {
    console.log(req.body);
    res.json(req.body);
});

配置选项:

javascript
app.use(express.json({
    limit: '10kb',
    strict: true,
    type: 'application/json'
}));

1.2 express.urlencoded() #

解析URL编码的请求体:

javascript
app.use(express.urlencoded({ extended: true }));

app.post('/form', (req, res) => {
    console.log(req.body);
    res.json(req.body);
});

配置选项:

javascript
app.use(express.urlencoded({
    extended: true,
    limit: '10kb',
    parameterLimit: 1000
}));

1.3 express.raw() #

解析原始请求体:

javascript
app.use(express.raw({ type: 'application/octet-stream', limit: '10mb' }));

app.post('/raw', (req, res) => {
    console.log(req.body);
    res.send(req.body);
});

1.4 express.text() #

解析文本请求体:

javascript
app.use(express.text({ type: 'text/plain' }));

app.post('/text', (req, res) => {
    console.log(req.body);
    res.send(req.body);
});

1.5 multer(文件上传) #

bash
npm install multer
javascript
const multer = require('multer');

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, 'uploads/');
    },
    filename: (req, file, cb) => {
        const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
        cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
    }
});

const upload = multer({
    storage: storage,
    limits: { fileSize: 5 * 1024 * 1024 },
    fileFilter: (req, file, cb) => {
        const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
        if (!allowedTypes.includes(file.mimetype)) {
            return cb(new Error('不支持的文件类型'), false);
        }
        cb(null, true);
    }
});

app.post('/upload', upload.single('avatar'), (req, res) => {
    res.json({ file: req.file });
});

app.post('/photos', upload.array('photos', 5), (req, res) => {
    res.json({ files: req.files });
});

app.post('/fields', upload.fields([
    { name: 'avatar', maxCount: 1 },
    { name: 'gallery', maxCount: 8 }
]), (req, res) => {
    res.json({ files: req.files });
});

二、安全中间件 #

2.1 helmet #

设置各种HTTP头以提高安全性:

bash
npm install helmet
javascript
const helmet = require('helmet');

app.use(helmet());

单独配置:

javascript
app.use(helmet({
    contentSecurityPolicy: {
        directives: {
            defaultSrc: ["'self'"],
            scriptSrc: ["'self'", "trusted-cdn.com"]
        }
    },
    crossOriginEmbedderPolicy: false
}));

helmet包含的安全头:

中间件 说明
contentSecurityPolicy 内容安全策略
crossOriginEmbedderPolicy 跨域嵌入策略
crossOriginOpenerPolicy 跨域打开策略
crossOriginResourcePolicy 跨域资源策略
dnsPrefetchControl DNS预取控制
frameguard 防止点击劫持
hidePoweredBy 隐藏X-Powered-By
hsts HTTP严格传输安全
ieNoOpen IE下载保护
noSniff 防止MIME类型嗅探
originAgentCluster Origin代理集群
permittedCrossDomainPolicies 跨域策略
referrerPolicy Referrer策略
xssFilter XSS过滤器

2.2 cors #

处理跨域资源共享:

bash
npm install cors
javascript
const cors = require('cors');

app.use(cors());

配置选项:

javascript
app.use(cors({
    origin: 'https://example.com',
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
    maxAge: 86400
}));

动态origin:

javascript
app.use(cors({
    origin: (origin, callback) => {
        const allowedOrigins = ['https://example.com', 'https://api.example.com'];
        if (!origin || allowedOrigins.includes(origin)) {
            callback(null, true);
        } else {
            callback(new Error('不允许的来源'));
        }
    }
}));

2.3 express-rate-limit #

请求限流:

bash
npm install express-rate-limit
javascript
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 100,
    message: { error: '请求过于频繁,请稍后再试' },
    standardHeaders: true,
    legacyHeaders: false
});

app.use('/api', limiter);

不同路由不同限制:

javascript
const apiLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 100
});

const loginLimiter = rateLimit({
    windowMs: 60 * 60 * 1000,
    max: 5,
    message: '登录尝试过多,请1小时后再试'
});

app.use('/api', apiLimiter);
app.use('/auth/login', loginLimiter);

三、日志中间件 #

3.1 morgan #

HTTP请求日志:

bash
npm install morgan
javascript
const morgan = require('morgan');

app.use(morgan('dev'));

日志格式:

格式 说明
combined Apache标准格式
common Apache通用格式
dev 开发格式(彩色)
short 短格式
tiny 最短格式

自定义格式:

javascript
morgan.token('user-id', (req) => req.user?.id || 'anonymous');

app.use(morgan(':method :url :status :response-time ms - :user-id'));

写入文件:

javascript
const fs = require('fs');
const path = require('path');

const accessLogStream = fs.createWriteStream(
    path.join(__dirname, 'access.log'),
    { flags: 'a' }
);

app.use(morgan('combined', { stream: accessLogStream }));

3.2 winston #

通用日志库:

bash
npm install winston
javascript
const winston = require('winston');

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
        new winston.transports.File({ filename: 'error.log', level: 'error' }),
        new winston.transports.File({ filename: 'combined.log' })
    ]
});

if (process.env.NODE_ENV !== 'production') {
    logger.add(new winston.transports.Console({
        format: winston.format.simple()
    }));
}

app.use((req, res, next) => {
    logger.info(`${req.method} ${req.url}`);
    next();
});

四、性能中间件 #

4.1 compression #

响应压缩:

bash
npm install compression
javascript
const compression = require('compression');

app.use(compression());

配置选项:

javascript
app.use(compression({
    filter: (req, res) => {
        if (req.headers['x-no-compression']) {
            return false;
        }
        return compression.filter(req, res);
    },
    threshold: 1024,
    level: 6
}));

4.2 response-time #

添加响应时间头:

bash
npm install response-time
javascript
const responseTime = require('response-time');

app.use(responseTime());

五、会话和认证中间件 #

Cookie解析:

bash
npm install cookie-parser
javascript
const cookieParser = require('cookie-parser');

app.use(cookieParser());
app.use(cookieParser('secret-key'));

app.get('/', (req, res) => {
    console.log('Cookies:', req.cookies);
    console.log('Signed Cookies:', req.signedCookies);
    res.send('OK');
});

app.get('/set-cookie', (req, res) => {
    res.cookie('name', 'express', { 
        maxAge: 900000, 
        httpOnly: true,
        signed: true
    });
    res.send('Cookie已设置');
});

5.2 express-session #

会话管理:

bash
npm install express-session
javascript
const session = require('express-session');

app.use(session({
    secret: 'your-secret-key',
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: process.env.NODE_ENV === 'production',
        maxAge: 24 * 60 * 60 * 1000
    }
}));

app.get('/login', (req, res) => {
    req.session.userId = 1;
    res.send('已登录');
});

app.get('/profile', (req, res) => {
    if (!req.session.userId) {
        return res.status(401).send('请先登录');
    }
    res.json({ userId: req.session.userId });
});

5.3 passport #

认证中间件:

bash
npm install passport passport-local
javascript
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(async (username, password, done) => {
    try {
        const user = await User.findOne({ username });
        if (!user || !user.comparePassword(password)) {
            return done(null, false, { message: '用户名或密码错误' });
        }
        return done(null, user);
    } catch (error) {
        return done(error);
    }
}));

passport.serializeUser((user, done) => {
    done(null, user.id);
});

passport.deserializeUser(async (id, done) => {
    try {
        const user = await User.findById(id);
        done(null, user);
    } catch (error) {
        done(error);
    }
});

app.use(session({ secret: 'secret' }));
app.use(passport.initialize());
app.use(passport.session());

app.post('/login', passport.authenticate('local'), (req, res) => {
    res.json({ user: req.user });
});

六、数据验证中间件 #

6.1 express-validator #

请求数据验证:

bash
npm install express-validator
javascript
const { body, param, query, validationResult } = require('express-validator');

const validate = (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(400).json({ errors: errors.array() });
    }
    next();
};

app.post('/users', [
    body('name').notEmpty().withMessage('用户名不能为空').trim().escape(),
    body('email').isEmail().withMessage('请输入有效的邮箱').normalizeEmail(),
    body('password').isLength({ min: 6 }).withMessage('密码至少6个字符'),
    body('age').optional().isInt({ min: 0, max: 150 }).toInt(),
    validate
], (req, res) => {
    res.status(201).json(req.body);
});

app.get('/users/:id', [
    param('id').isMongoId().withMessage('无效的ID'),
    validate
], (req, res) => {
    res.json({ id: req.params.id });
});

app.get('/search', [
    query('q').notEmpty().withMessage('搜索关键词不能为空'),
    query('page').optional().isInt({ min: 1 }).toInt(),
    query('limit').optional().isInt({ min: 1, max: 100 }).toInt(),
    validate
], (req, res) => {
    res.json(req.query);
});

七、静态文件中间件 #

7.1 express.static #

javascript
app.use(express.static('public'));

app.use('/static', express.static('public'));

app.use('/uploads', express.static('uploads', {
    maxAge: '1d',
    etag: false
}));

7.2 serve-favicon #

网站图标:

bash
npm install serve-favicon
javascript
const favicon = require('serve-favicon');

app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));

八、完整配置示例 #

javascript
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const cookieParser = require('cookie-parser');
const session = require('express-session');

const app = express();

app.set('trust proxy', true);

app.use(helmet());

app.use(cors({
    origin: process.env.CORS_ORIGIN || '*',
    credentials: true
}));

if (process.env.NODE_ENV === 'development') {
    app.use(morgan('dev'));
} else {
    app.use(morgan('combined'));
}

app.use(compression());

const limiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 100,
    standardHeaders: true,
    legacyHeaders: false
});
app.use('/api', limiter);

app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));

app.use(cookieParser(process.env.COOKIE_SECRET));

app.use(session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: process.env.NODE_ENV === 'production',
        httpOnly: true,
        maxAge: 24 * 60 * 60 * 1000
    }
}));

app.use(express.static('public'));

app.get('/api/health', (req, res) => {
    res.json({ status: 'ok' });
});

app.use((req, res) => {
    res.status(404).json({ error: '路由未找到' });
});

app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({ error: '服务器错误' });
});

module.exports = app;

九、总结 #

常用中间件一览:

类别 中间件 用途
请求解析 express.json JSON解析
请求解析 multer 文件上传
安全 helmet 安全头设置
安全 cors 跨域处理
安全 express-rate-limit 请求限流
日志 morgan HTTP日志
性能 compression 响应压缩
会话 express-session 会话管理
认证 passport 用户认证
验证 express-validator 数据验证

下一步,让我们学习请求与响应对象!

最后更新:2026-03-28