一、Express 适合做什么
- 传统web网站
- API 接口服务器
- 服务端渲染中间层
- 开发辅助工具
- 自定义集成框架
二、Express 安装与使用
- 可以在当前项目中安装
npm install express
- 快速创建一个应用的骨架
npx express-generator
- 简单使用
const express = require('express');
const app = express();
// 中间件 解析 JSON 请求体
app.use(express.json());
// 处理 GET 请求
app.get('/user/:id', (req, res) => {
res.send(`用户ID: ${req.params.id}`);
});
// 处理 POST 请求
app.post('/login', express.json(), (req, res) => {
const { username, password } = req.body;
res.json({ status: 'success', user: username });
});
// 监听3000端口服务
app.listen(3000,()=>{
console.log("run http://127.0.0.1:3000")
})
三、Express 路由
1、 路由的方法
| 方法 | 描述 | 示例 |
|---|---|---|
app.get() | 处理 GET 请求 | 获取资源 |
app.post() | 处理 POST 请求 | 创建资源 |
app.put() | 处理 PUT 请求 | 更新整个资源 |
app.patch() | 处理 PATCH 请求 | 部分更新资源 |
app.delete() | 处理 DELETE 请求 | 删除资源 |
app.all() | 处理所有 HTTP 方法请求 | 通用处理 |
const express = require('express');
const app = express();
// GET 方法示例
app.get('/api/users', (req, res) => {
res.send('获取所有用户');
});
// POST 方法示例
app.post('/api/users', (req, res) => {
res.status(201).send('创建新用户');
});
// PUT 方法示例
app.put('/api/users/:id', (req, res) => {
res.send(`更新用户 ${req.params.id}`);
});
// DELETE 方法示例
app.delete('/api/users/:id', (req, res) => {
res.send(`删除用户 ${req.params.id}`);
});
// PATCH 方法示例
app.patch('/api/users/:id', (req, res) => {
res.send(`部分更新用户 ${req.params.id}`);
});
// ALL 方法示例 - 处理所有方法
app.all('/api/status', (req, res) => {
res.send(`通过 ${req.method} 方法访问状态页面`);
});
2、路由参数
- 基本路由参数
app.get('/users/:userId', (req, res) => {
res.send(`用户ID: ${req.params.userId}`);
});
- 多个路由参数
app.get('/posts/:year/:month/:day', (req, res) => {
const { year, month, day } = req.params;
res.send(`发布日期: ${year}年${month}月${day}日`);
});
- 可选路由参数
app.get('/books/:category?', (req, res) => {
if (req.params.category) {
res.send(`分类: ${req.params.category}`);
} else {
res.send('所有书籍');
}
});
- 通配符路由参数
app.get('/files/*', (req, res) => {
const filePath = req.params[0]; // 获取通配符匹配的部分
res.send(`文件路径: ${filePath}`);
});
- 正则表达式路由参数
// 只匹配数字ID
app.get('/articles/:id(\\d+)', (req, res) => {
res.send(`文章ID: ${req.params.id} (数字)`);
});
// 匹配特定格式
app.get('/date/:year(\\d{4})-:month(\\d{2})-:day(\\d{2})', (req, res) => {
res.send(`日期: ${req.params.year}-${req.params.month}-${req.params.day}`);
});
-
使用中间件处理通用参数
// 验证用户ID的中间件
const validateUserId = (req, res, next) => {
const userId = parseInt(req.params.userId);
if (isNaN(userId) || userId <= 0) {
return res.status(400).json({ error: '无效的用户ID' });
}
req.validatedUserId = userId;
next();
};
// 应用中间件
app.get('/users/:userId/profile', validateUserId, (req, res) => {
// 使用验证后的ID
const userId = req.validatedUserId;
// 获取用户资料...
});
-
路由参数预处理
app.param('userId', (req, res, next, userId) => {
// 从数据库获取用户
User.findById(userId, (err, user) => {
if (err) return next(err);
if (!user) return res.status(404).send('用户未找到');
req.user = user;
next();
});
});
app.get('/users/:userId', (req, res) => {
res.json(req.user);
});
-
路由参数 vs 查询参数
| 特性 | 路由参数 | 查询参数 |
|---|---|---|
| 位置 | URL路径中 | URL ? 后 |
| 访问 | req.params | req.query |
| 示例 | /users/123 | /users?id=123 |
| 用途 | 标识资源 | 过滤、排序等 |
| 可见性 | 公开 | 公开 |
| SEO影响 | 较高 | 较低 |
-
路由参数与RESTful API
RESTful API
// RESTful 资源路由
app.get('/api/users', (req, res) => {
// 获取所有用户
});
app.post('/api/users', (req, res) => {
// 创建新用户
});
app.get('/api/users/:id', (req, res) => {
// 获取特定用户
});
app.put('/api/users/:id', (req, res) => {
// 更新用户
});
app.delete('/api/users/:id', (req, res) => {
// 删除用户
});
3、路由模块化
使用该类express.Router创建模块化、可挂载的路由处理程序
以下示例创建一个路由器作为模块,在其中加载一个中间件功能,定义一些路由,并将路由器模块挂载到主应用程序中的路径上。
在app目录下创建一个名为router的文件users.js,内容如下:
const express = require('express');
const router = express.Router();
// 用户列表
router.get('/', (req, res) => {
res.json([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
});
// 用户详情
router.get('/details:id', (req, res) => {
res.json({ id: req.params.id, name: 'User' + req.params.id });
});
module.exports = router;
const userRouter = require('./routes/users');
app.use('/users', userRouter);
该应用程序现在将能够处理对/users和的请求/users/details/12
四、Express 响应处理
| 方法 | 描述 |
|---|---|
| res.download() | 提示要下载的文件。 |
| res.end() | 结束响应过程。 |
| res.json() | 发送 JSON 响应。 |
| res.jsonp() | 发送具有 JSONP 支持的 JSON 响应。 |
| res.redirect() | 重定向请求。 |
| res.render() | 渲染视图模板。 |
| res.send() | 发送各种类型的回应。 |
| res.sendFile() | 以八位字节流的形式发送文件。 |
| res.sendStatus() | 设置响应状态代码并将其字符串表示形式作为响应主体发送。 |
res.send([body])- 发送各种类型的数据,自动设置正确的 Content-Type 头
// 发送文本
res.send('Hello World!');
// 发送 HTML
res.send('<h1>Express 响应</h1>');
// 发送 JSON 对象(自动转换为 JSON 字符串)
res.send({ message: 'Success', code: 200 });
// 发送数组
res.send([1, 2, 3]);
// 发送 Buffer(自动设置 Content-Type)
res.send(Buffer.from('some data'));
res.json([body])- 专门用于发送 JSON 响应
res.json({
success: true,
data: { id: 1, name: 'Alice' }
});
// 设置状态码 + JSON
res.status(201).json({ id: 2, name: 'Bob' });
res.status()- 设置 HTTP 状态码(通常与其他响应方法链式调用)
res.status(404).send('Page not found');
res.status(500).json({ error: 'Internal server error' });
res.status(201).send('Resource created');
res.sendStatus()- 状态码 + 默认消息
res.sendStatus(200); // 等同于 res.status(200).send('OK')
res.sendStatus(404); // 等同于 res.status(404).send('Not Found')
res.sendStatus(500); // 等同于 res.status(500).send('Internal Server Error')
res.redirect([status,] path)- 将客户端重定向到新 URL:
// 相对路径重定向
res.redirect('/new-location');
// 绝对路径重定向
res.redirect('https://example.com');
// 带状态码的重定向(默认为 302)
res.redirect(301, '/permanent-redirect');
// 回退重定向
res.redirect('back'); // 重定向到 Referer 头中的地址
res.render(view [, locals] [, callback])- 渲染view并将渲染后的 HTML 字符串发送到客户端
// send the rendered view to the client
res.render('index')
// if a callback is specified, the rendered HTML string has to be sent explicitly
res.render('index', function (err, html) {
res.send(html)
})
// pass a local variable to the view
res.render('user', { name: 'Tobi' }, function (err, html) {
// ...
})
res.end([data] [, encoding])- 结束响应过程
res.end()
res.status(404).end()
res.sendFile(path [, options] [, fn])- 发送文件
const path = require('path');
// 发送文件(自动设置 Content-Type)
res.sendFile('/path/to/file.pdf');
// 设置文件名(下载时使用)
res.sendFile('/path/to/report.pdf', {
root: process.cwd(),
headers: {
'Content-Disposition': 'attachment; filename="monthly-report.pdf"'
}
});
res.download(path [, filename] [, options] [, fn])- 文件下载
// 基本下载
res.download('/path/to/file.zip');
// 设置下载文件名
res.download('/path/to/document.pdf', 'report.pdf');
// 错误处理回调
res.download('/path/to/file', 'filename.ext', (err) => {
if (err) {
// 处理错误(如文件不存在)
} else {
console.log('文件已发送');
}
});
res.set(field [, value])设置响应头
// 设置单个响应头
res.set('Content-Type', 'text/html');
// 设置多个响应头
res.set({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-Custom-Header': 'value'
});
### res.type(type)设置响应头中Content-Type
// 设置 Content-Type 快捷方式
res.type('html'); // => 'text/html'
res.type('json'); // => 'application/json'
res.type('png'); // => 'image/png'
res.type('application/xml'); // 完整类型
res.jsonp([body])- 发送支持 JSONP 的 JSON 响应。此方法与 相同res.json(),只是它支持 JSONP 回调。
res.jsonp(null)
// => callback(null)
res.jsonp({ user: 'tobi' })
// => callback({ "user": "tobi" })
res.status(500).jsonp({ error: 'message' })
// => callback({ "error": "message" })
五、Express 中间件
-
Express 中间件,是在请求和响应之间执行的一系列函数。每个中间件函数都可以访问请求对象
req、响应对象res和next函数(next用于调用下一个中间件)。 -
中间件类型
1、
应用层中间件:绑定到 app 对象2、
路由层中间件:绑定到 router 对象3、
错误处理中间件:处理错误(4个参数)4、
内置中间件:Express 自带5、
第三方中间件:社区提供
中间件使用(应用和路由)
- 全局中间件
const express = require('express');
const app = express();
// 注册全局中间件(所有请求都会执行)
app.use((req, res, next) => {
console.log('全局中间件 - 每个请求都会执行');
next();
});
- 特定路径中间件
// 只对 /api 开头的路径生效
app.use('/api', (req, res, next) => {
console.log('API 请求中间件');
next();
});
- 特定路径和请求方式中间件
// 只对 GET /users 请求生效
app.get('/users', (req, res, next) => {
console.log('用户路由中间件');
next();
}, (req, res) => {
res.send('用户列表');
});
- 多个中间件
const logRequest = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
};
const authCheck = (req, res, next) => {
if (req.headers.authorization) {
next();
} else {
res.status(401).send('未授权');
}
};
// 组合使用多个中间件
app.get('/secure', logRequest, authCheck, (req, res) => {
res.send('安全内容');
});
常用内置中间件
express.json()- 解析 JSON 请求体
app.use(express.json()); // 解析 application/json
// 使用
app.post('/users', (req, res) => {
console.log(req.body); // 访问解析后的JSON数据
res.json(req.body);
});
express.urlencoded()- 解析 URL 编码的请求体
app.use(express.urlencoded({ extended: true })); // 解析 application/x-www-form-urlencoded
// 使用
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 处理登录逻辑
});
express.static()- 托管静态文件
app.use(express.static('public')); // 托管 public 目录
// 访问方式:http://localhost:3000/images/logo.png
exoress.Router()- 创建模块化路由
const router = express.Router();
// 路由特定中间件
router.use((req, res, next) => {
console.log('路由中间件');
next();
});
router.get('/', (req, res) => {
res.send('用户主页');
});
app.use('/users', router); // 挂载路由
常用第三方中间件
morgan- HTTP 请求日志
npm install helmet
const helmet = require('helmet');
app.use(helmet()); // 设置安全相关的 HTTP 头
helmet- 安全头部
npm install helmet
const helmet = require('helmet');
app.use(helmet()); // 设置安全相关的 HTTP 头
cors- 跨域支持
npm install cors
const cors = require('cors');
app.use(cors()); // 启用 CORS
cookie-parser- Cookie 解析
npm install cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser()); // 解析 Cookie
compression-响应压缩
npm install compression
const compression = require('compression');
app.use(compression()); // 压缩响应数据
自定义中间件开发
请求日志中间件
const requestLogger = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.originalUrl} - ${res.statusCode} [${duration}ms]`);
});
next();
};
app.use(requestLogger);
认证中间件
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: '未提供认证令牌' });
}
const token = authHeader.split(' ')[1];
// 验证 token (示例)
if (token === 'valid-token') {
req.user = { id: 1, name: 'John Doe' }; // 附加用户信息到请求对象
next();
} else {
res.status(403).json({ error: '无效令牌' });
}
};
// 使用
app.get('/profile', authenticate, (req, res) => {
res.json({ user: req.user });
});
API 响应格式化中间件
app.use((req, res, next) => {
// 成功响应方法
res.apiSuccess = (data, status = 200) => {
res.status(status).json({
success: true,
data
});
};
// 错误响应方法
res.apiError = (message, status = 400) => {
res.status(status).json({
success: false,
error: message
});
};
next();
});
// 使用
app.get('/products', (req, res) => {
try {
const products = getProducts(); // 获取产品
res.apiSuccess(products);
} catch (error) {
res.apiError(error.message, 500);
}
});
错误处理中间件
基本结构
// 错误处理中间件必须有4个参数
app.use((err, req, res, next) => {
console.error(err.stack);
const status = err.statusCode || 500;
const message = process.env.NODE_ENV === 'production'
? '服务器错误'
: err.message;
res.status(status).json({
error: {
status,
message
}
});
});
增强版错误处理
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
// 全局错误处理中间件
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
if (process.env.NODE_ENV === 'development') {
res.status(err.statusCode).json({
status: 'error',
message: err.message,
stack: err.stack,
error: err
});
} else {
// 生产环境:不泄露堆栈信息
res.status(err.statusCode).json({
status: 'error',
message: err.isOperational ? err.message : '发生意外错误'
});
}
});
// 在路由中使用
app.get('/error', (req, res, next) => {
// 抛出可操作错误
next(new AppError('测试错误', 400));
// 抛出不可操作错误
// throw new Error('未处理错误');
});
中间件例子
中间件执行顺序
// 1. 安全相关中间件
app.use(helmet());
app.use(cors());
// 2. 请求解析中间件
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// 3. 自定义中间件
app.use(requestLogger);
app.use(authenticate);
// 4. 路由
app.use('/api/v1', apiRouter);
// 5. 404 处理
app.use((req, res, next) => {
res.status(404).json({
status: 'fail',
message: `未找到 ${req.originalUrl}`
});
});
// 6. 全局错误处理(必须放在最后)
app.use(globalErrorHandler);
可配置中间件
// 可配置的缓存中间件
const cacheMiddleware = (duration) => {
return (req, res, next) => {
res.set('Cache-Control', `public, max-age=${duration}`);
next();
};
};
// 使用
app.get('/static-data', cacheMiddleware(3600), (req, res) => {
res.json({ data: '缓存数据' });
});
中间件执行流程图
graph TD
A[客户端请求] --> B[安全中间件 helmet, cors]
B --> C[请求解析中间件 json, urlencoded]
C --> D[自定义中间件 logger, auth]
D --> E[路由处理]
E --> F{是否调用 next error?}
F -->|是| G[错误处理中间件]
F -->|否| H[发送响应]
G --> I[发送错误响应]
H --> J[结束]
I --> J