Express.js 是 Node.js 生态系统中最流行、最成熟的 Web 应用框架。它提供了极简且灵活的 API,使得构建 Web 服务器和 RESTful API 变得快速而简单。
一、Express 简介与安装
什么是 Express
Express 是一个基于 Node.js 的极简且灵活的 Web 应用程序框架,提供了一组强大的功能,适用于 Web 和移动应用程序的开发。它构建在 Node.js 的 HTTP 模块之上,简化了服务器端应用程序的开发过程。
安装 Express
在使用 Express 之前,需要先安装它。Express 通过 npm(Node Package Manager)进行安装和管理。
bash
# 创建项目目录
mkdir my-express-app
cd my-express-app
# 初始化项目(创建 package.json)
npm init -y
# 安装 Express
npm install express
二、创建第一个 Express 应用
创建一个 Express 应用非常简单,只需要调用 express() 函数即可。这个函数返回一个 Express 应用实例,它是整个应用的核心。
最简单的 Express 应用
javascript
// 引入 Express 模块
const express = require('express');
// 创建 Express 应用实例
const app = express();
// 定义路由
app.get('/', (req, res) => {
res.send('Hello World!');
});
// 启动服务器,监听指定端口
const PORT = 3000;
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});
express() 函数
express() 是一个工厂函数,用于创建 Express 应用实例。这个实例包含了所有 Express 的功能,包括路由、中间件、模板引擎等。
javascript
const express = require('express');
const app = express(); // 创建应用实例
// app 对象现在包含了所有 Express 的方法和属性
console.log(typeof app); // 'function'(实际上是一个函数对象)
app.listen() 方法
app.listen() 方法用于启动服务器并开始监听指定端口的 HTTP 请求。它是 Express 应用启动的关键方法。
基本语法:
javascript
app.listen(port, callback)
参数说明:
port: 要监听的端口号(数字)- 端口号范围:有效端口号为 0-65535
- 推荐端口:开发环境常用 3000、3001、8000、8080、5000 等
- 不能使用的端口 :
- 0-1023 :系统保留端口(需要管理员权限),常见的有:
- 80:HTTP(通常需要 root 权限)
- 443:HTTPS(通常需要 root 权限)
- 22:SSH
- 21:FTP
- 25:SMTP
- 3306:MySQL
- 5432:PostgreSQL
- 1024-49151:注册端口(IANA 注册),应避免与已知服务冲突
- 49152-65535:动态/私有端口(相对安全,适合开发使用)
- 0-1023 :系统保留端口(需要管理员权限),常见的有:
- 注意事项 :如果端口已被占用,会抛出
EADDRINUSE错误
callback: 服务器启动后执行的回调函数(可选)
示例:
javascript
const express = require('express');
const app = express();
// 方式一:只指定端口
app.listen(3000);
// 方式二:指定端口和回调函数(推荐)
app.listen(3000, () => {
console.log('服务器运行在 3000 端口');
});
// 方式三:使用环境变量配置端口
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`服务器运行在 ${PORT} 端口`);
});
使用 Node.js HTTP 模块(底层方式):
Express 的 app.listen() 实际上是对 Node.js http.createServer() 的封装。你也可以直接使用 HTTP 模块:
javascript
const express = require('express');
const http = require('http');
const app = express();
const server = http.createServer(app);
server.listen(3000, () => {
console.log('服务器运行在 3000 端口');
});
三、路由基础
路由是 Express 应用的核心功能之一。它定义了应用如何响应客户端对不同 URL 路径和 HTTP 方法的请求。
什么是路由
路由是指确定应用程序如何响应客户端对特定端点的请求,该端点是 URI(或路径)和特定的 HTTP 请求方法(GET、POST、PUT、DELETE 等)的组合。
路由的基本结构
每个路由都有一个或多个处理函数,当路由匹配时执行:
javascript
app.METHOD(path, handler)
app: Express 应用实例METHOD: HTTP 请求方法(get、post、put、delete 等)path: 服务器上的路径handler: 路由匹配时执行的函数
HTTP 方法路由
Express 支持所有 HTTP 方法,最常用的是 GET、POST、PUT 和 DELETE。
app.get() - GET 请求
GET 请求用于获取资源,是最常用的 HTTP 方法。
javascript
// 获取所有用户
app.get('/users', (req, res) => {
res.json([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]);
});
// 获取单个用户
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ id: userId, name: 'John' });
});
app.post() - POST 请求
POST 请求用于创建新资源。
javascript
// 需要配置中间件解析请求体
app.use(express.json());
// 创建用户
app.post('/users', (req, res) => {
const { name, email } = req.body;
// 创建用户的逻辑...
res.status(201).json({
id: 1,
name: name,
email: email,
message: '用户创建成功'
});
});
app.put() - PUT 请求
PUT 请求用于更新整个资源。
javascript
app.use(express.json());
// 更新用户(完整更新)
app.put('/users/:id', (req, res) => {
const userId = req.params.id;
const { name, email } = req.body;
// 更新用户的逻辑...
res.json({
id: userId,
name: name,
email: email,
message: '用户更新成功'
});
});
app.delete() - DELETE 请求
DELETE 请求用于删除资源。
javascript
// 删除用户
app.delete('/users/:id', (req, res) => {
const userId = req.params.id;
// 删除用户的逻辑...
res.status(204).send(); // 204 No Content
});
其他 HTTP 方法
Express 还支持其他 HTTP 方法:
javascript
// PATCH - 部分更新
app.patch('/users/:id', (req, res) => {
res.json({ message: '部分更新用户' });
});
// OPTIONS - 预检请求
// 使用场景:主要用于 CORS(跨域资源共享)场景
// - 当前端应用(如 http://localhost:3000)请求后端 API(如 http://localhost:8080)时
// - 当请求使用了 PUT、DELETE、PATCH 等非简单方法,或包含自定义请求头时
// 解决的问题:
// - 浏览器安全策略:浏览器会阻止跨域请求,除非服务器明确允许
// - 告知客户端服务器支持哪些 HTTP 方法和请求头
app.options('/users', (req, res) => {
res.set('Allow', 'GET, POST, OPTIONS');
res.send();
});
// HEAD - 获取响应头
app.head('/users', (req, res) => {
res.end();
});
路由路径匹配
Express 支持多种路由路径匹配方式,包括字符串匹配、正则表达式匹配和参数匹配。
字符串路径匹配
最简单的路由路径是字符串匹配:
javascript
// 精确匹配
app.get('/about', (req, res) => {
res.send('关于页面');
});
// 匹配根路径
app.get('/', (req, res) => {
res.send('首页');
});
路径参数匹配
使用 : 定义路径参数:
javascript
// 单个参数
app.get('/users/:id', (req, res) => {
res.json({ userId: req.params.id });
});
// 多个参数
app.get('/users/:userId/posts/:postId', (req, res) => {
res.json({
userId: req.params.userId,
postId: req.params.postId
});
});
可选参数
使用 ? 定义可选参数:
javascript
// /users 和 /users/:id 都可以匹配
app.get('/users/:id?', (req, res) => {
if (req.params.id) {
res.json({ userId: req.params.id });
} else {
res.json({ message: '所有用户' });
}
});
通配符匹配
使用 * 进行通配符匹配:
javascript
// 匹配 /users/ 后面的所有路径
app.get('/users/*', (req, res) => {
res.send('用户相关页面');
});
正则表达式匹配
使用正则表达式进行复杂匹配:
javascript
// 只匹配数字 ID
app.get('/users/:id(\\d+)', (req, res) => {
res.json({ userId: req.params.id });
});
// 匹配特定格式
app.get('/files/:filename(.*\\.(jpg|png|gif))', (req, res) => {
res.send(`图片文件: ${req.params.filename}`);
});
多个处理函数
一个路由可以有多个处理函数(中间件),按顺序执行:
javascript
// 验证函数
function validateUser(req, res, next) {
const userId = req.params.id;
if (!userId || isNaN(userId)) {
return res.status(400).json({ error: '无效的用户 ID' });
}
next(); // 继续执行下一个处理函数
}
// 获取用户函数
function getUser(req, res) {
const userId = req.params.id;
res.json({ id: userId, name: 'John' });
}
// 使用多个处理函数
app.get('/users/:id', validateUser, getUser);
四、请求对象(req):获取请求数据
在 Express 中,每个路由处理函数都会接收请求对象(req),它包含了客户端发送的所有信息,包括请求头、请求参数、请求体等。
req.params - 路径参数
路径参数是 URL 路径中的动态部分,使用 : 定义,通过 req.params 对象访问。
基本用法
javascript
// 定义路径参数
app.get('/users/:id', (req, res) => {
console.log(req.params); // { id: '123' }
console.log(req.params.id); // '123'
res.json({
userId: req.params.id,
message: '获取用户信息'
});
});
// 访问 /users/123
// req.params = { id: '123' }
多个路径参数
一个路由可以定义多个路径参数:
javascript
app.get('/users/:userId/posts/:postId', (req, res) => {
console.log(req.params);
// 访问 /users/123/posts/456
// req.params = { userId: '123', postId: '456' }
res.json({
userId: req.params.userId,
postId: req.params.postId
});
});
路径参数命名规则
路径参数的名称可以是字母、数字和下划线的组合:
javascript
// 有效
app.get('/users/:id', handler);
app.get('/users/:userId', handler);
app.get('/users/:user_id', handler);
// 无效(不能包含特殊字符)
// app.get('/users/:user-id', handler); // 错误
req.query - 查询参数
查询参数是 URL 中 ? 后面的键值对,通过 req.query 对象访问。查询参数用于过滤、排序、分页等操作。
基本用法
javascript
// 访问 /search?q=express&page=1
app.get('/search', (req, res) => {
console.log(req.query); // { q: 'express', page: '1' }
console.log(req.query.q); // 'express'
console.log(req.query.page); // '1'
res.json({
query: req.query.q,
page: req.query.page
});
});
多个查询参数
javascript
// 访问 /products?category=electronics&minPrice=100&maxPrice=500&sort=price
app.get('/products', (req, res) => {
const { category, minPrice, maxPrice, sort } = req.query;
console.log('分类:', category);
console.log('最低价格:', minPrice);
console.log('最高价格:', maxPrice);
console.log('排序:', sort);
// 使用查询参数进行过滤和排序...
res.json({
category: category,
minPrice: minPrice,
maxPrice: maxPrice,
sort: sort
});
});
数组查询参数
查询参数可以是数组:
javascript
// 访问 /products?tags=javascript&tags=nodejs&tags=express
app.get('/products', (req, res) => {
console.log(req.query.tags); // ['javascript', 'nodejs', 'express']
res.json({
tags: req.query.tags
});
});
查询参数的默认值
为查询参数提供默认值:
javascript
app.get('/products', (req, res) => {
const page = parseInt(req.query.page) || 1; // 默认第 1 页
const limit = parseInt(req.query.limit) || 10; // 默认每页 10 条
const sort = req.query.sort || 'id'; // 默认按 id 排序
res.json({
page: page,
limit: limit,
sort: sort
});
});
req.body - 请求体
请求体包含 POST、PUT 等请求中发送的数据。需要中间件来解析(如 express.json())。
javascript
// 需要先配置中间件
app.use(express.json());
app.post('/users', (req, res) => {
console.log(req.body); // { name: 'John', email: 'john@example.com' }
console.log(req.body.name); // 'John'
res.json({
id: 1,
name: req.body.name,
email: req.body.email
});
});
req 的其他常用属性
req.headers - 请求头
请求头包含了客户端发送的所有 HTTP 头信息。
javascript
app.get('/', (req, res) => {
console.log(req.headers);
console.log(req.headers['user-agent']); // 浏览器信息
console.log(req.headers['content-type']); // 内容类型
});
req.method - HTTP 方法
获取请求的 HTTP 方法(GET、POST、PUT、DELETE 等)。
javascript
app.use((req, res, next) => {
console.log(`请求方法: ${req.method}`);
console.log(`请求路径: ${req.path}`);
next();
});
req.path - 请求路径
获取请求的路径部分(不包含查询字符串)。
javascript
// 访问 /users/123?page=1
app.get('/users/:id', (req, res) => {
console.log(req.path); // '/users/123'
});
req.url - 完整 URL
获取请求的完整 URL(包含查询字符串)。
javascript
// 访问 /users/123?page=1
app.get('/users/:id', (req, res) => {
console.log(req.url); // '/users/123?page=1'
});
req.ip - 客户端 IP 地址
获取客户端的 IP 地址。
javascript
app.get('/', (req, res) => {
console.log('客户端 IP:', req.ip);
res.send(`你的 IP 地址是: ${req.ip}`);
});
路径参数 vs 查询参数
理解路径参数和查询参数的区别和使用场景很重要:
路径参数(req.params):
- 用于标识资源(如用户 ID、文章 ID)
- URL 的一部分,必需
- 示例:
/users/123(123 是用户 ID)
查询参数(req.query):
- 用于过滤、排序、分页等操作
- URL 的可选部分
- 示例:
/users?page=1&limit=10(page 和 limit 用于分页)
五、响应对象(res):发送响应
响应对象(res)代表 HTTP 响应,用于向客户端发送数据、设置响应头、设置状态码等。
res.send() - 通用响应方法
res.send() 是最灵活的响应方法,可以发送各种类型的数据。Express 会根据数据类型自动设置适当的 Content-Type。
javascript
app.get('/', (req, res) => {
res.send('Hello World!'); // 发送字符串,Content-Type: text/html
});
app.get('/json', (req, res) => {
res.send({ message: 'Hello' }); // 发送对象,Content-Type: application/json
});
app.get('/buffer', (req, res) => {
res.send(Buffer.from('Hello')); // 发送 Buffer,Content-Type: application/octet-stream
});
app.get('/api/users', (req, res) => {
res.send([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]); // 发送数组,Content-Type: application/json
});
res.json() - JSON 响应
res.json() 专门用于发送 JSON 响应,会自动设置 Content-Type 为 application/json,并调用 JSON.stringify()。
javascript
app.get('/api/user', (req, res) => {
res.json({
id: 1,
name: 'John Doe',
email: 'john@example.com'
});
});
app.get('/api/users', (req, res) => {
res.json([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]);
});
app.get('/api/user/:id', (req, res) => {
res.json({
id: req.params.id,
name: 'John Doe',
profile: {
age: 30,
city: 'New York'
},
tags: ['developer', 'nodejs']
});
});
res.status() - 设置状态码
res.status() 用于设置 HTTP 响应状态码,通常与其他响应方法链式调用。
基本用法
javascript
// 成功响应(200)
app.get('/success', (req, res) => {
res.status(200).json({ message: '成功' });
});
// 创建成功(201)
app.post('/users', (req, res) => {
res.status(201).json({ id: 1, name: 'John' });
});
// 未找到(404)
app.get('/not-found', (req, res) => {
res.status(404).json({ error: '资源未找到' });
});
// 服务器错误(500)
app.get('/error', (req, res) => {
res.status(500).json({ error: '服务器内部错误' });
});
常用 HTTP 状态码
javascript
// 2xx - 成功
res.status(200).json({ message: 'OK' }); // 成功
res.status(201).json({ message: 'Created' }); // 创建成功
res.status(204).send(); // 无内容(常用于 DELETE)
// 4xx - 客户端错误
res.status(400).json({ error: 'Bad Request' }); // 请求错误
res.status(401).json({ error: 'Unauthorized' }); // 未授权
res.status(403).json({ error: 'Forbidden' }); // 禁止访问
res.status(404).json({ error: 'Not Found' }); // 未找到
res.status(409).json({ error: 'Conflict' }); // 冲突
// 5xx - 服务器错误
res.status(500).json({ error: 'Internal Server Error' }); // 服务器错误
res.status(503).json({ error: 'Service Unavailable' }); // 服务不可用
res.redirect() - 重定向
res.redirect() 用于将客户端重定向到另一个 URL。这对于页面跳转、URL 重写等场景非常有用。
javascript
// 临时重定向(302)
app.get('/old-page', (req, res) => {
res.redirect('/new-page');
});
// 永久重定向(301)
app.get('/old-url', (req, res) => {
res.redirect(301, '/new-url');
});
// 重定向到外部 URL
app.get('/external', (req, res) => {
res.redirect('https://www.example.com');
});
// 重定向到相对路径
app.get('/login', (req, res) => {
// 登录成功后重定向到首页
res.redirect('/');
});
app.get('/users/:id/edit', (req, res) => {
// 编辑完成后重定向到用户详情页
const userId = req.params.id;
res.redirect(`/users/${userId}`);
});
res 的其他常用方法
res.set() / res.header() - 设置响应头
设置 HTTP 响应头。
javascript
app.get('/', (req, res) => {
res.set('Content-Type', 'text/html');
res.set('X-Custom-Header', 'custom-value');
res.send('<h1>Hello</h1>');
});
// 或者使用 res.header()
app.get('/api', (req, res) => {
res.header('Content-Type', 'application/json');
res.json({ message: 'Hello' });
});
res.get() - 获取响应头
获取已设置的响应头值。
javascript
app.get('/', (req, res) => {
res.set('X-Custom-Header', 'custom-value');
console.log(res.get('X-Custom-Header')); // 'custom-value'
res.send('Hello');
});
res.cookie() - 设置 Cookie
设置 Cookie。
javascript
app.get('/set-cookie', (req, res) => {
res.cookie('username', 'john', { maxAge: 900000, httpOnly: true });
res.send('Cookie 已设置');
});
res.write() 和 res.end() - 分块写入和结束响应
res.write():用于分块写入响应数据,可以多次调用 res.end():用于结束响应
javascript
app.get('/chunked', (req, res) => {
res.write('Hello');
res.write(' ');
res.write('World');
res.end('!'); // 结束响应
});
注意事项:
res.write()可以多次调用,但必须最后调用res.end()来结束响应- 如果使用
res.send()或res.json(),它们会自动结束响应,不需要手动调用res.end()
res.render() - 渲染模板
javascript
// 需要配置模板引擎(如 EJS)
app.set('view engine', 'ejs');
app.get('/', (req, res) => {
res.render('index', { title: '首页', users: [] });
});
res.download() - 下载文件
res.download() 用于将文件作为附件下载发送给客户端,会自动设置适当的响应头。客户端访问后,浏览器会弹出下载对话框,用户可以选择保存文件。
javascript
app.get('/download', (req, res) => {
res.download('/path/to/file.pdf', 'document.pdf'); // 第一个参数是文件路径,第二个参数是下载时的文件名(可选)
});
res.sendFile() - 发送文件
javascript
const path = require('path');
app.get('/file', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
响应方法链式调用
多个响应方法可以链式调用:
javascript
app.get('/api/user', (req, res) => {
res
.status(200)
.set('X-Custom-Header', 'custom-value')
.json({
id: 1,
name: 'John Doe'
});
});
六、完整示例:RESTful API
下面是一个完整的 RESTful API 示例,展示了 Express 的各种功能的使用:
javascript
const express = require('express');
const app = express();
// 配置中间件解析 JSON 请求体
app.use(express.json());
// 模拟数据存储
let users = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];
// GET /users - 获取所有用户(支持分页和过滤)
app.get('/users', (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const role = req.query.role; // 可选的角色过滤
res.status(200).json({
success: true,
page: page,
limit: limit,
count: users.length,
data: users
});
});
// GET /users/:id - 获取单个用户
app.get('/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
const user = users.find(u => u.id === userId);
if (!user) {
return res.status(404).json({
success: false,
error: '用户未找到'
});
}
res.status(200).json({
success: true,
data: user
});
});
// POST /users - 创建用户
app.post('/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({
success: false,
error: '缺少必要字段:name 和 email'
});
}
const newUser = {
id: users.length + 1,
name: name,
email: email
};
users.push(newUser);
res.status(201).json({
success: true,
message: '用户创建成功',
data: newUser
});
});
// PUT /users/:id - 更新用户(完整更新)
app.put('/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
const userIndex = users.findIndex(u => u.id === userId);
if (userIndex === -1) {
return res.status(404).json({
success: false,
error: '用户未找到'
});
}
const { name, email } = req.body;
users[userIndex] = { ...users[userIndex], name, email };
res.status(200).json({
success: true,
message: '用户更新成功',
data: users[userIndex]
});
});
// DELETE /users/:id - 删除用户
app.delete('/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
const userIndex = users.findIndex(u => u.id === userId);
if (userIndex === -1) {
return res.status(404).json({
success: false,
error: '用户未找到'
});
}
users.splice(userIndex, 1);
res.status(204).send(); // 204 No Content
});
// 获取用户的文章列表(路径参数 + 查询参数)
app.get('/users/:userId/posts', (req, res) => {
const userId = req.params.userId; // 路径参数
const page = parseInt(req.query.page) || 1; // 查询参数
const limit = parseInt(req.query.limit) || 10; // 查询参数
const sort = req.query.sort || 'createdAt'; // 查询参数
res.json({
userId: userId,
page: page,
limit: limit,
sort: sort,
posts: []
});
});
// 重定向示例
app.get('/home', (req, res) => {
res.redirect('/');
});
app.get('/', (req, res) => {
res.send('欢迎访问用户管理 API');
});
// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});
总结
Express.js 作为 Node.js 最流行的 Web 框架,提供了简洁而强大的 API 来构建 Web 应用和 RESTful API。本文深入探讨了 Express 的基础概念:
- Express 简介与安装:了解 Express 是什么以及如何安装
- 创建第一个 Express 应用 :通过
express()创建应用实例,使用app.listen()启动服务器 - 路由基础 :使用
app.get()、app.post()、app.put()、app.delete()等方法定义路由,支持路径参数、查询参数等多种匹配方式 - 请求对象(req) :通过
req.params获取路径参数,通过req.query获取查询参数,通过req.body获取请求体数据 - 响应对象(res) :使用
res.send()、res.json()、res.status()、res.redirect()等方法向客户端发送响应 - 完整示例:构建一个完整的 RESTful API,综合运用所学知识