ExpressJS - 速查表
现代 Express.js 速查表
Section titled “现代 Express.js 速查表”在这份速查表中,我们将涵盖 Express.js 的核心功能和概念,以在 2024 年构建健壮且可扩展的应用程序。我们将探讨路由、中间件、现代异步模式和数据处理。你将学习如何使用现代工具链设置项目、处理各种 HTTP 方法、管理静态资产以及使用最佳实践与数据库集成。本指南是您使用 Express.js 构建高效 API 和 Web 应用程序的快速参考。
- 简介
- 现代项目设置
- 核心概念:路由
- 核心概念:中间件
- 内容服务:视图与静态文件
- 请求和响应对象
- 使用
express.Router进行模块化路由 - 应用程序方法
- 现代错误处理
- 调试和日志记录
- 使用 Async/Await 进行数据库集成
- 使用 JWT 进行安全身份验证
- 基本安全实践
- 使用现代工具进行测试
- 部署到云端
Express.js 是一个极简且灵活的 Node.js Web 应用程序框架,它为 Web 和移动应用程序提供了强大的功能集。它充当基本 Web 应用程序功能的薄层,而不会掩盖你熟悉和喜爱的 Node.js 功能。
现代项目设置
Section titled “现代项目设置”1. 初始化你的项目
Section titled “1. 初始化你的项目”为你的项目创建一个新目录,进入该目录,并初始化一个 package.json 文件。此文件将跟踪你项目的元数据和依赖项。
$ mkdir my-modern-app$ cd my-modern-app$ npm init -y2. 安装 Express
Section titled “2. 安装 Express”安装 Express 并将其添加到你的项目依赖项中。
$ npm install express3. 创建入口文件 (index.js)
Section titled “3. 创建入口文件 (index.js)”此代码使用现代 ES 模块语法设置了一个基本的 Express 服务器。它监听由环境变量定义的端口(最佳实践),并响应一个 JSON 消息。要使用 ES 模块语法 (import/export),请在 package.json 中添加 "type": "module"。
import express from 'express';
// 最佳实践:使用环境变量进行配置const PORT = process.env.PORT || 3000;
const app = express();
app.get('/', (req, res) => { res.json({ message: 'Hello Modern World!' });});
app.listen(PORT, () => { console.log(`Server is listening on port ${PORT}`);});4. 运行应用程序
Section titled “4. 运行应用程序”在开发过程中,强烈建议使用像 nodemon 这样的工具,它会在文件更改时自动重启服务器。
# 将 nodemon 作为开发依赖项安装$ npm install --save-dev nodemon
# 在你的 package.json 中添加一个脚本# "scripts": {# "dev": "nodemon index.js"# }
# 运行开发服务器$ npm run dev核心概念:路由
Section titled “核心概念:路由”路由定义了应用程序如何响应客户端对特定端点的请求,该端点是 URI(或路径)和特定的 HTTP 请求方法(GET、POST 等)。
每个路由可以有一个或多个处理函数,这些函数在路由匹配时执行。
// GET 方法路由app.get('/', (req, res) => res.send('GET request to the homepage'));
// POST 方法路由app.post('/users', (req, res) => res.send('POST request to create a user'));
// 带有参数的动态路由app.get('/users/:userId', (req, res) => { res.send(`Details for user ${req.params.userId}`);});路由处理程序
Section titled “路由处理程序”你可以提供多个回调函数,它们像中间件一样处理请求。唯一的例外是这些回调可以调用 next('route') 来绕过剩余的路由回调。
const logTimestamp = (req, res, next) => { console.log('Time: ', Date.now()); next(); // 将控制权传递给下一个处理程序};
app.get('/with-handler', [logTimestamp], (req, res) => { res.send('Check the console for a timestamp!');});核心概念:中间件
Section titled “核心概念:中间件”中间件函数是那些可以访问请求对象 (req)、响应对象 (res) 以及应用程序请求-响应周期中的 next 函数的函数。它们可以执行任何代码,对请求和响应对象进行更改,结束请求-响应周期,并调用堆栈中的下一个中间件。
常见内置中间件
Section titled “常见内置中间件”Express 包含用于常见任务的内置中间件。自 Express 4.16+ 起,流行的 body-parser 已被包含在内。
// 用于解析 JSON 请求体的中间件app.use(express.json());
// 用于解析 URL 编码请求体的中间件app.use(express.urlencoded({ extended: true }));
// 用于从 'public' 目录提供静态文件的中间件app.use(express.static('public'));自定义中间件
Section titled “自定义中间件”编写你自己的中间件很简单。这是一个记录请求方法和路径的日志器。
const requestLogger = (req, res, next) => { console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`); next(); // 将控制权传递给下一个处理程序};
app.use(requestLogger);内容服务:视图与静态文件
Section titled “内容服务:视图与静态文件”虽然许多现代 Express 应用程序充当前端客户端(如 React 或 Vue)的 JSON API,但它也可以使用模板引擎在服务器上渲染动态 HTML。
1. 安装并设置视图引擎
Section titled “1. 安装并设置视图引擎”Pug、EJS 和 Handlebars 是流行的选择。以下是设置 EJS 的方法。
$ npm install ejs
// 在你的 app.js 中app.set('view engine', 'ejs');// 可选:指定模板所在的目录// app.set('views', path.join(__dirname, 'views'));2. 创建模板(例如 views/profile.ejs)
Section titled “2. 创建模板(例如 views/profile.ejs)”模板可以嵌入从路由处理程序传递的动态数据。
<!DOCTYPE html><html><head> <title><%= title %></title></head><body> <h1>Hello, <%= user.name %>!</h1></body></html>3. 渲染模板
Section titled “3. 渲染模板”使用 res.render() 渲染视图并将渲染后的 HTML 字符串发送到客户端。
app.get('/profile', (req, res) => { const userData = { name: 'Alex' }; res.render('profile', { title: 'User Profile', user: userData });});请求和响应对象
Section titled “请求和响应对象”路由处理程序和中间件可以访问 req 和 res 对象,它们分别代表 HTTP 请求和响应。
请求对象 (req) 属性
Section titled “请求对象 (req) 属性”| 属性 | 说明 |
|---|---|
req.params | 路由参数(例如,/users/:id) |
req.query | 查询字符串参数(例如,?sort=desc) |
req.body | 解析后的请求体(需要 express.json() 或 express.urlencoded()) |
req.headers | 传入的请求头 |
req.method | 请求的 HTTP 方法(GET、POST 等) |
req.originalUrl | 请求的完整原始 URL |
响应对象 (res) 方法
Section titled “响应对象 (res) 方法”| 方法 | 说明 |
|---|---|
res.send() | 发送各种类型的响应(字符串、Buffer、对象) |
res.json() | 发送 JSON 响应(自动设置 Content-Type) |
res.status() | 设置响应的 HTTP 状态码 |
res.render() | 渲染视图模板 |
res.redirect() | 重定向到指定的 URL |
res.cookie() | 设置 Cookie |
示例:链式调用状态和 JSON
Section titled “示例:链式调用状态和 JSON”app.get('/api/health', (req, res) => { res.status(200).json({ status: 'UP' });});使用 express.Router 进行模块化路由
Section titled “使用 express.Router 进行模块化路由”对于大型应用程序,最佳实践是使用 express.Router 将路由处理程序分组到单独的文件中,从而保持主应用程序文件的整洁。
1. 创建路由文件(例如 routes/userRoutes.js)
Section titled “1. 创建路由文件(例如 routes/userRoutes.js)”import { Router } from 'express';
const router = Router();
// 此路由现在实际上是 /api/users/router.get('/', (req, res) => { res.send('List of all users');});
// 此路由现在实际上是 /api/users/profilerouter.get('/profile', (req, res) => { res.send('User profile page');});
export default router;2. 在你的主应用程序文件中挂载路由
Section titled “2. 在你的主应用程序文件中挂载路由”import express from 'express';import userRoutes from './routes/userRoutes.js';
const app = express();
// 将路由挂载到 /api/users 路径app.use('/api/users', userRoutes);
// ... 其他应用程序设置 ...app.listen(3000);应用程序方法
Section titled “应用程序方法”| 方法 | 说明 |
|---|---|
app.use([path], callback) | 在指定路径挂载中间件函数 |
app.get(path, callback) | 处理 GET 请求 |
app.post(path, callback) | 处理 POST 请求 |
app.put(path, callback) | 处理 PUT 请求 |
app.delete(path, callback) | 处理 DELETE 请求 |
app.listen(port, [callback]) | 将应用程序绑定到端口并启动服务器 |
app.set(name, value) | 将设置名称 name 赋值为 value |
app.engine(ext, callback) | 将给定的模板引擎回调注册为 ext |
现代错误处理
Section titled “现代错误处理”集中式错误处理中间件是一种最佳实践。它应该在所有其他 app.use() 和路由调用之后定义。这个特殊的中间件有四个参数(err、req、res、next)。
// 未匹配路由的 404 处理程序app.use((req, res, next) => { res.status(404).send('Sorry, that route does not exist.');});
// 集中式错误处理程序app.use((err, req, res, next) => { console.error(err.stack); // 在生产环境中避免泄露堆栈跟踪 const statusCode = err.statusCode || 500; const message = process.env.NODE_ENV === 'production' ? 'Something broke!' : err.message; res.status(statusCode).json({ error: message });});调试和日志记录
Section titled “调试和日志记录”有效的调试和日志记录对于开发和生产至关重要。
使用 Node.js 检查器
Section titled “使用 Node.js 检查器”使用 --inspect 标志运行你的应用程序,以启用 Chrome DevTools 或 VS Code 调试器等工具进行调试。这允许你设置断点并实时检查变量。
$ node --inspect index.js结构化日志记录
Section titled “结构化日志记录”对于生产环境,简单的 console.log 是不够的。使用 pino 或 winston 等结构化日志库来获取可过滤、机器可读的日志。
$ npm install pino
// 示例用法import pino from 'pino';const logger = pino({ level: 'info' });
logger.info('Server started successfully.');logger.error({ err }, 'An error occurred during request processing.');使用 Async/Await 进行数据库集成
Section titled “使用 Async/Await 进行数据库集成”现代 Node.js 大力推崇使用 async/await 来处理数据库查询等异步操作。这使得代码比回调或 .then() 链更清晰、更容易理解。
使用 pg 的 PostgreSQL
Section titled “使用 pg 的 PostgreSQL”pg 库是连接 PostgreSQL 的标准。它完全支持 Promise,非常适合 async/await。
$ npm install pg
// db.jsimport pg from 'pg';const { Pool } = pg;
const pool = new Pool({ connectionString: process.env.DATABASE_URL, // 例如:postgresql://user:password@host:port/database});
export default pool;
// 在路由中使用import pool from './db.js';
app.get('/users', async (req, res, next) => { try { const { rows } = await pool.query('SELECT * FROM users'); res.json(rows); } catch (err) { next(err); // 将错误传递给中心处理程序 }});使用 mongodb 驱动程序的 MongoDB
Section titled “使用 mongodb 驱动程序的 MongoDB”原生的 mongodb 驱动程序是完全异步优先的。
$ npm install mongodb
// mongo.jsimport { MongoClient } from 'mongodb';
const uri = process.env.MONGO_URI; // 例如:mongodb://localhost:27017const client = new MongoClient(uri);
let db;
async function connectToDatabase() { try { await client.connect(); db = client.db('my-app-db'); console.log('Connected to MongoDB'); } catch (err) { console.error('Failed to connect to MongoDB', err); process.exit(1); }}
export { connectToDatabase, db };
// 在路由中使用import { db } from './mongo.js';
app.get('/products', async (req, res, next) => { try { const products = await db.collection('products').find({}).toArray(); res.json(products); } catch (err) { next(err); }});使用 JWT 进行安全身份验证
Section titled “使用 JWT 进行安全身份验证”JSON Web Tokens (JWT) 是一种在 API 中处理无状态身份验证的标准方式。
1. 安装依赖项
Section titled “1. 安装依赖项”# JWT 用于令牌创建,bcrypt 用于密码哈希$ npm install jsonwebtoken bcryptjs2. 用户注册和登录(控制器逻辑)
Section titled “2. 用户注册和登录(控制器逻辑)”此逻辑使用 async/await 实现清晰可读的代码,并使用 try/catch 进行错误处理。至关重要的是,像 JWT_SECRET 这样的密钥必须存储在环境变量中,而不是硬编码。
import bcrypt from 'bcryptjs';import jwt from 'jsonwebtoken';// 假设用户模型来自 Mongoose 或 Prisma 等数据库 ORMimport User from '../models/User.js';
export const register = async (req, res, next) => { try { const { email, password, name } = req.body; const hashedPassword = await bcrypt.hash(password, 12);
const newUser = new User({ email, name, password: hashedPassword }); await newUser.save();
res.status(201).json({ message: 'User created!' }); } catch (err) { next(err); }};
export const login = async (req, res, next) => { try { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user) { return res.status(401).json({ message: 'Authentication failed' }); }
const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { return res.status(401).json({ message: 'Authentication failed' }); }
const token = jwt.sign( { id: user._id, email: user.email }, process.env.JWT_SECRET, // 使用环境变量! { expiresIn: '1h' } );
res.status(200).json({ token }); } catch (err) { next(err); }};3. 身份验证中间件
Section titled “3. 身份验证中间件”此中间件通过验证 Authorization 头部中的 JWT 来保护路由。
import jwt from 'jsonwebtoken';
export const protect = (req, res, next) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ message: 'No token, authorization denied' }); }
const token = authHeader.split(' ')[1]; const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; // 将解码后的用户 payload 添加到请求中 next(); } catch (err) { res.status(401).json({ message: 'Token is not valid' }); }};
// 用法:// app.get('/api/protected-route', protect, (req, res) => { ... });基本安全实践
Section titled “基本安全实践”安全不是事后才考虑的事情。从这些基本的中间件和实践开始。
使用 Helmet 设置安全头
Section titled “使用 Helmet 设置安全头”helmet 设置各种 HTTP 头,以帮助保护你的应用程序免受已知 Web 漏洞的侵害。
$ npm install helmet
// 在 app.js 中import helmet from 'helmet';app.use(helmet());速率限制以防止滥用
Section titled “速率限制以防止滥用”使用 express-rate-limit 来防止暴力破解攻击和拒绝服务攻击。
$ npm install express-rate-limit
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 分钟 max: 100, // 将每个 IP 的请求限制为每个时间窗口 100 次 standardHeaders: true, // 在 `RateLimit-*` 头中返回速率限制信息 legacyHeaders: false, // 禁用 `X-RateLimit-*` 头});
// 将速率限制中间件应用于所有请求app.use(limiter);基于会话的应用程序的 CSRF 保护
Section titled “基于会话的应用程序的 CSRF 保护”跨站请求伪造 (CSRF) 是使用 Cookie/会话的传统 Web 应用程序关注的问题。对于在头部使用 JWT 的无状态 API,CSRF 的风险较小。如果你正在构建一个使用会话的服务器渲染应用程序,则需要像 csurf 的继任者这样的库或双重提交 Cookie 模式。对于现代 SPA,请确保你正在使用 SameSite Cookie 和正确的 CORS 策略。
使用现代工具进行测试
Section titled “使用现代工具进行测试”测试运行器(如 Jest 或 Vitest)和 HTTP 断言库(Supertest)的组合是测试 Express 应用程序的强大设置。
1. 安装依赖项
Section titled “1. 安装依赖项”我们将使用 Vitest 的速度和现代特性,以及 Supertest 来向我们的应用程序发出请求。
$ npm install --save-dev vitest supertest2. 为测试构建你的应用程序结构
Section titled “2. 为测试构建你的应用程序结构”将应用程序创建与服务器监听部分分开。这允许你在不启动服务器的情况下将 app 对象导入到测试中。
import express from 'express';const app = express();app.get('/', (req, res) => res.status(200).send('OK'));export default app;
// index.js (或 server.js)import app from './app.js';const PORT = 3000;app.listen(PORT, () => console.log(`Listening on port ${PORT}`));3. 编写集成测试 (app.test.js)
Section titled “3. 编写集成测试 (app.test.js)”此测试使用 supertest 对内存中的应用程序发出实际 HTTP 请求,并使用 vitest 定义测试套件和断言。
import { describe, it, expect } from 'vitest';import request from 'supertest';import app from '../app.js'; // 导入 Express 应用程序
describe('GET /', () => { it('should respond with a 200 status code and OK text', async () => { const response = await request(app).get('/'); expect(response.statusCode).toBe(200); expect(response.text).toBe('OK'); });});
describe('GET /non-existent-route', () => { it('should respond with a 404 status code', async () => { const response = await request(app).get('/not-a-real-route'); expect(response.statusCode).toBe(404); });});像 Vercel、Render 和 Heroku 这样的平台使得部署 Node.js 应用程序变得简单。以下是 Vercel 的快速指南,它在无服务器部署方面表现出色。
1. 适用于 Vercel 的结构
Section titled “1. 适用于 Vercel 的结构”Vercel 可以自动将你的 Express 应用程序转换为无服务器函数。将你的主 Express 应用程序文件放在 /api 目录中。
my-modern-app/├── api/│ └── index.js <-- 你的 Express 应用程序代码在这里├── public/ <-- 静态文件(可选)└── package.json2. 更新你的 Express 应用程序 (api/index.js)
Section titled “2. 更新你的 Express 应用程序 (api/index.js)”确保你的应用程序导出了 app 对象。Vercel 的构建过程将处理服务器创建。
import express from 'express';const app = express();
app.get('/api/hello', (req, res) => { res.send('Hello from Express on Vercel!');});
// 导出应用程序export default app;3. 添加 vercel.json 用于路由
Section titled “3. 添加 vercel.json 用于路由”此配置文件告诉 Vercel 将所有传入请求定向到你的无服务器 Express 应用程序。
{ "version": 2, "rewrites": [ { "source": "/api/(.*)", "destination": "/api" } ]}安装 Vercel CLI,链接你的项目,然后进行部署。
# 全局安装 Vercel CLI$ npm install -g vercel
# 从项目根目录部署$ vercel
# 部署到生产环境$ vercel --prod