一、Express 简介
1.1 框架特点
Express 是 Node.js 平台上最流行、最成熟的 Web 应用框架,具有以下特点:
| 特点 |
说明 |
| 轻量级 |
核心功能简洁,无冗余设计 |
| 中间件架构 |
强大的中间件生态,灵活扩展 |
| 路由系统 |
清晰的路由定义,支持 RESTful 设计 |
| 模板引擎 |
支持多种模板引擎(EJS、Pug、Nunjucks 等) |
| 生态丰富 |
npm 上有大量 Express 中间件和插件 |
| 文档完善 |
官方文档详尽,社区活跃 |
1.2 安装 Express
1 2 3 4 5 6 7 8 9
| mkdir my-express-app cd my-express-app
npm init -y
npm install express
|
1.3 Hello World 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const express = require('express'); const app = express(); const PORT = 3000;
app.get('/', (req, res) => { res.send('Hello, Express!'); });
app.listen(PORT, () => { console.log(`Server is running at http://localhost:${PORT}`); });
|
运行:
1.4 Express Generator 脚手架
Express 提供了官方脚手架工具快速生成项目结构:
1 2 3 4 5 6 7 8 9 10 11 12
| npm install -g express-generator
express myapp --view=ejs
cd myapp npm install
npm start
|
生成的项目结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| myapp/ ├── bin/ │ └── www # 启动脚本 ├── public/ # 静态资源 │ ├── images/ │ ├── javascripts/ │ └── stylesheets/ ├── routes/ # 路由文件 │ ├── index.js │ └── users.js ├── views/ # 模板文件 │ ├── index.ejs │ └── error.ejs ├── app.js # 应用入口 └── package.json
|
二、路由系统
2.1 路由方法
Express 支持所有 HTTP 请求方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| const express = require('express'); const app = express();
app.get('/users', (req, res) => { res.json({ method: 'GET', message: '获取用户列表' }); });
app.post('/users', (req, res) => { res.json({ method: 'POST', message: '创建用户' }); });
app.put('/users/:id', (req, res) => { res.json({ method: 'PUT', message: `更新用户 ${req.params.id}` }); });
app.patch('/users/:id', (req, res) => { res.json({ method: 'PATCH', message: `部分更新用户 ${req.params.id}` }); });
app.delete('/users/:id', (req, res) => { res.json({ method: 'DELETE', message: `删除用户 ${req.params.id}` }); });
app.all('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); });
app.listen(3000);
|
2.2 路由参数
路径参数(Route Params)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| app.get('/users/:id', (req, res) => { console.log(req.params); res.json({ userId: req.params.id }); });
app.get('/posts/:postId/comments/:commentId', (req, res) => { console.log(req.params); res.json(req.params); });
app.get('/books/:id?', (req, res) => { if (req.params.id) { res.json({ bookId: req.params.id }); } else { res.json({ books: [] }); } });
|
查询参数(Query String)
1 2 3 4 5 6 7 8 9 10 11 12 13
| app.get('/search', (req, res) => { console.log(req.query);
const { keyword, page = 1, limit = 10 } = req.query; res.json({ keyword, page: parseInt(page), limit: parseInt(limit), results: [] }); });
|
2.3 路由模块化
使用 express.Router 实现路由模块化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const express = require('express'); const router = express.Router();
router.get('/', (req, res) => { res.json({ users: [] }); });
router.post('/', (req, res) => { res.json({ message: '用户创建成功' }); });
router.get('/:id', (req, res) => { res.json({ userId: req.params.id }); });
router.put('/:id', (req, res) => { res.json({ message: `用户 ${req.params.id} 更新成功` }); });
router.delete('/:id', (req, res) => { res.json({ message: `用户 ${req.params.id} 删除成功` }); });
module.exports = router;
|
1 2 3 4 5 6 7 8 9 10
| const express = require('express'); const usersRouter = require('./routes/users');
const app = express();
app.use('/users', usersRouter);
app.listen(3000);
|
三、中间件
3.1 中间件概念
中间件(Middleware)是 Express 的核心机制,它是一个函数,可以访问请求对象(req)、响应对象(res)和 next 函数。
1 2 3 4
| 请求 ──▶ 中间件1 ──▶ 中间件2 ──▶ ... ──▶ 路由处理 ──▶ 响应 │ ▼ next()
|
3.2 应用级中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const express = require('express'); const app = express();
app.use((req, res, next) => { const start = Date.now(); console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
const duration = Date.now() - start; console.log(`请求耗时: ${duration}ms`); });
app.use('/api', (req, res, next) => { console.log('API 请求拦截'); next(); });
app.get('/', (req, res) => { res.send('Home Page'); });
app.get('/api/data', (req, res) => { res.json({ data: 'api data' }); });
app.listen(3000);
|
3.3 路由级中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const checkAuth = (req, res, next) => { const token = req.headers['authorization']; if (!token) { return res.status(401).json({ error: '未提供认证令牌' }); } req.userId = 'user123'; next(); };
app.get('/profile', checkAuth, (req, res) => { res.json({ userId: req.userId, name: '张三' }); });
app.get('/settings', checkAuth, (req, res) => { res.json({ userId: req.userId, theme: 'dark' }); });
|
3.4 错误处理中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| app.use((err, req, res, next) => { console.error('错误:', err.message);
if (err.name === 'ValidationError') { return res.status(400).json({ error: '参数验证失败', details: err.message }); }
if (err.name === 'UnauthorizedError') { return res.status(401).json({ error: '认证失败' }); }
res.status(500).json({ error: '服务器内部错误' }); });
|
3.5 常用中间件
morgan(日志记录)
1 2 3 4 5 6 7
| const morgan = require('morgan');
app.use(morgan('dev'));
|
body-parser(请求体解析)
1 2 3 4 5 6 7 8 9 10
| const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.text());
|
cookie-parser
1
| npm install cookie-parser
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/set-cookie', (req, res) => { res.cookie('token', 'abc123', { httpOnly: true, maxAge: 3600000 }); res.send('Cookie 已设置'); });
app.get('/get-cookie', (req, res) => { console.log(req.cookies); res.json(req.cookies); });
|
3.6 自定义中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| const authMiddleware = (req, res, next) => { const token = req.headers['authorization']?.replace('Bearer ', '');
if (!token) { return res.status(401).json({ error: '请提供认证令牌' }); }
try { const decoded = Buffer.from(token, 'base64').toString(); req.user = { id: decoded }; next(); } catch (error) { return res.status(401).json({ error: '令牌无效' }); } };
const rateLimit = (maxRequests, windowMs) => { const requests = new Map();
return (req, res, next) => { const ip = req.ip; const now = Date.now();
if (!requests.has(ip)) { requests.set(ip, []); }
const userRequests = requests.get(ip); const recentRequests = userRequests.filter(time => now - time < windowMs);
if (recentRequests.length >= maxRequests) { return res.status(429).json({ error: '请求过于频繁,请稍后再试' }); }
recentRequests.push(now); requests.set(ip, recentRequests); next(); }; };
app.use(rateLimit(100, 60000)); app.use('/api/private', authMiddleware);
|
四、请求与响应
4.1 req 对象常用属性和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| app.get('/request-info', (req, res) => { const info = { method: req.method, url: req.url, originalUrl: req.originalUrl, protocol: req.protocol, hostname: req.hostname, ip: req.ip, headers: req.headers, query: req.query, params: req.params, body: req.body, cookies: req.cookies, session: req.session };
res.json(info); });
|
4.2 res 对象常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| app.get('/text', (req, res) => { res.send('Hello World'); });
app.get('/json', (req, res) => { res.json({ name: '张三', age: 25 }); });
app.get('/html', (req, res) => { res.send('<h1>Hello HTML</h1>'); });
app.get('/file', (req, res) => { res.sendFile('/path/to/file.pdf'); });
app.get('/status', (req, res) => { res.status(201).json({ message: 'Created' }); });
app.get('/headers', (req, res) => { res.set('X-Custom-Header', 'value'); res.json({ message: 'Headers set' }); });
app.get('/redirect', (req, res) => { res.redirect('https://example.com'); });
app.get('/download', (req, res) => { res.download('/path/to/file.pdf', 'report.pdf'); });
|
4.3 静态文件服务
1 2 3 4 5 6 7 8 9 10 11 12 13
| const express = require('express'); const path = require('path'); const app = express();
app.use(express.static('public'));
app.use('/static', express.static(path.join(__dirname, 'public')));
|
五、错误处理
5.1 404 处理
1 2 3 4 5 6 7
| app.use((req, res) => { res.status(404).json({ error: 'Not Found', path: req.path }); });
|
5.2 全局错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| app.get('/sync-error', (req, res) => { throw new Error('同步错误示例'); });
app.get('/async-error', async (req, res, next) => { try { const data = await someAsyncOperation(); res.json(data); } catch (error) { next(error); } });
app.use((err, req, res, next) => { console.error('全局错误:', err);
const statusCode = err.statusCode || 500;
res.status(statusCode).json({ error: err.message, stack: process.env.NODE_ENV === 'development' ? err.stack : undefined }); });
|
5.3 自定义错误类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 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); } }
class NotFoundError extends AppError { constructor(message = '资源不存在') { super(message, 404); } }
class ValidationError extends AppError { constructor(message = '参数验证失败') { super(message, 400); } }
class UnauthorizedError extends AppError { constructor(message = '认证失败') { super(message, 401); } }
module.exports = { AppError, NotFoundError, ValidationError, UnauthorizedError };
|
使用自定义错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const { NotFoundError, ValidationError } = require('./errors/AppError');
app.get('/users/:id', (req, res, next) => { const user = findUserById(req.params.id);
if (!user) { return next(new NotFoundError(`用户 ${req.params.id} 不存在`)); }
res.json(user); });
app.post('/users', (req, res, next) => { if (!req.body.name) { return next(new ValidationError('用户名不能为空')); }
res.status(201).json({ message: '创建成功' }); });
|
六、实战练习
6.1 RESTful API 服务实现
项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13
| restful-api/ ├── config/ │ └── db.js ├── controllers/ │ └── userController.js ├── middleware/ │ └── errorHandler.js ├── models/ │ └── User.js ├── routes/ │ └── users.js ├── app.js └── package.json
|
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const express = require('express'); const morgan = require('morgan'); const bodyParser = require('body-parser');
const usersRouter = require('./routes/users'); const errorHandler = require('./middleware/errorHandler');
const app = express();
app.use(morgan('dev')); app.use(bodyParser.json());
app.use('/api/users', usersRouter);
app.use((req, res, next) => { const error = new Error(`Not Found - ${req.originalUrl}`); error.statusCode = 404; next(error); });
app.use(errorHandler);
const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });
|
models/User.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| let users = [ { id: 1, name: '张三', email: 'zhangsan@example.com', age: 25 }, { id: 2, name: '李四', email: 'lisi@example.com', age: 30 }, { id: 3, name: '王五', email: 'wangwu@example.com', age: 28 } ];
let nextId = 4;
module.exports = { findAll: () => [...users], findById: (id) => users.find(u => u.id === parseInt(id)), create: (userData) => { const newUser = { id: nextId++, ...userData }; users.push(newUser); return newUser; }, update: (id, userData) => { const index = users.findIndex(u => u.id === parseInt(id)); if (index === -1) return null; users[index] = { ...users[index], ...userData }; return users[index]; }, delete: (id) => { const index = users.findIndex(u => u.id === parseInt(id)); if (index === -1) return false; users.splice(index, 1); return true; } };
|
controllers/userController.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| const User = require('../models/User'); const { NotFoundError, ValidationError } = require('../errors/AppError');
exports.getUsers = (req, res) => { const { page = 1, limit = 10, name } = req.query;
let users = User.findAll();
if (name) { users = users.filter(u => u.name.includes(name)); }
const start = (parseInt(page) - 1) * parseInt(limit); const end = start + parseInt(limit); const paginatedUsers = users.slice(start, end);
res.json({ data: paginatedUsers, meta: { total: users.length, page: parseInt(page), limit: parseInt(limit), totalPages: Math.ceil(users.length / parseInt(limit)) } }); };
exports.getUser = (req, res, next) => { const user = User.findById(req.params.id);
if (!user) { return next(new NotFoundError(`用户 ${req.params.id} 不存在`)); }
res.json(user); };
exports.createUser = (req, res, next) => { const { name, email, age } = req.body;
if (!name || !email) { return next(new ValidationError('姓名和邮箱为必填项')); }
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return next(new ValidationError('邮箱格式不正确')); }
const newUser = User.create({ name, email, age }); res.status(201).json(newUser); };
exports.updateUser = (req, res, next) => { const { name, email, age } = req.body;
const updatedUser = User.update(req.params.id, { name, email, age });
if (!updatedUser) { return next(new NotFoundError(`用户 ${req.params.id} 不存在`)); }
res.json(updatedUser); };
exports.deleteUser = (req, res, next) => { const deleted = User.delete(req.params.id);
if (!deleted) { return next(new NotFoundError(`用户 ${req.params.id} 不存在`)); }
res.status(204).send(); };
|
routes/users.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const express = require('express'); const router = express.Router(); const userController = require('../controllers/userController');
router.get('/', userController.getUsers);
router.get('/:id', userController.getUser);
router.post('/', userController.createUser);
router.put('/:id', userController.updateUser);
router.delete('/:id', userController.deleteUser);
module.exports = router;
|
middleware/errorHandler.js
1 2 3 4 5 6 7 8 9 10 11 12 13
| module.exports = (err, req, res, next) => { console.error('错误详情:', err);
const statusCode = err.statusCode || 500; const message = err.isOperational ? err.message : '服务器内部错误';
res.status(statusCode).json({ status: 'error', statusCode, message, ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) }); };
|
6.2 API 测试
使用 curl 或 Postman 测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| curl http://localhost:3000/api/users
curl "http://localhost:3000/api/users?page=1&limit=2"
curl http://localhost:3000/api/users/1
curl -X POST http://localhost:3000/api/users \ -H "Content-Type: application/json" \ -d '{"name":"赵六","email":"zhaoliu@example.com","age":35}'
curl -X PUT http://localhost:3000/api/users/1 \ -H "Content-Type: application/json" \ -d '{"name":"张三(已更新)","age":26}'
curl -X DELETE http://localhost:3000/api/users/1
|
💡 小结:Express 框架以其简洁的设计和强大的中间件生态成为 Node.js Web 开发的首选。掌握路由系统、中间件机制和错误处理后,你可以快速构建任何规模的 Web 应用。