Skip to content

Node.js Express 框架

Express (或 Express.js) 是一个极简、灵活且非常流行的 Node.js Web 应用框架。它提供了一层薄薄的基础 Web 应用功能,而不掩盖 Node.js 的特性,使其非常适合快速构建强大的 API 和 Web 应用。

Express 的核心特性:

  • 路由 (Routing): 定义应用程序如何根据 URL (路径) 和 HTTP 方法 (GET, POST, PUT, DELETE 等) 响应客户端请求。
  • 中间件 (Middleware): 提供一个强大的机制,用于在请求-响应周期中按顺序执行函数。中间件可以执行诸如日志记录、解析请求体、身份验证、授权、错误处理等任务。
  • 请求/响应辅助方法 (Request/Response Helpers): 为 Node.js 原生的请求 (req) 和响应 (res) 对象添加了实用方法和属性,简化了常见的 Web 开发任务。
  • 模板引擎集成 (Templating Engine Integration): 轻松集成各种模板引擎(如 EJS、Pug、Handlebars)以渲染动态 HTML 页面。
  • 专注于性能 (Focus on Performance): Express 设计得轻量且快速。

首先,确保您已初始化一个带有 package.json 文件的 Node.js 项目 (npm init -y)。然后,将 Express 安装为项目依赖项:

$ npm install express

此命令会安装 Express 并将其添加到您的 package.json 中的 dependencies。

虽然 Express 处理核心的 Web 框架特性,但您通常需要额外的中间件来完成特定任务。一些常见的中间件包括:

  • cors: 启用跨域资源共享 (Cross-Origin Resource Sharing)。
  • helmet: 通过设置各种 HTTP 头来帮助保护您的 Express 应用。
  • morgan: HTTP 请求日志记录中间件。
  • dotenv: 从 .env 文件加载环境变量。
  • multer: 用于处理 multipart/form-data 的中间件,主要用于文件上传。

按需安装它们,例如:$ npm install cors morgan

注意:基本的请求体解析 (JSON, URL 编码) 现在已内置到 Express 中,因此您通常不再需要单独的 body-parser 包。

这是一个极简的 Express 应用程序,它启动一个服务器监听 3000 端口,并响应对主页 (/) 的请求。

// Import the express library
const express = require('express');
// Create an Express application instance
const app = express();
// Define the port the server will listen on
const port = 3000;
// Define a route handler for GET requests to the root path ('/')
app.get('/', (req, res) => {
// req: request object, res: response object
res.send('Hello World from Express!');
});
// Start the server and listen for connections on the specified port
app.listen(port, () => {
console.log(`Example Express app listening at http://localhost:${port}`);
});

将此代码保存到文件中(例如,app.js)并运行它:

$ node app.js

您将在终端中看到以下输出:

Example Express app listening at http://localhost:3000

打开您的 Web 浏览器并访问 http://localhost:3000/。您应该看到文本 “Hello World from Express!”。如果您访问任何其他路径(例如,http://localhost:3000/other),Express 将自动响应 404 Not Found(未找到)错误。

Express 路由处理程序接收两个主要对象:

  • 请求对象 (req): 表示传入的 HTTP 请求。它包含以下信息:
    • req.params: 路由参数(例如,/users/:userId)。
    • req.query: URL 查询参数(例如,/search?term=node)。
    • req.body: 解析后的请求体(需要请求体解析中间件,如 express.json() 或 express.urlencoded())。
    • req.headers: 传入的请求头。
    • req.method: HTTP 方法(GET、POST 等)。
    • req.path: 请求路径。
    • req.ip: 客户端的 IP 地址。
  • 响应对象 (res): 表示 Express 应用发送回的 HTTP 响应。它提供了用于发送响应的方法,例如:
    • res.send(): 发送各种类型的响应(HTML、文本、JSON 会自动字符串化)。
    • res.json(): 发送 JSON 响应。
    • res.status(): 设置 HTTP 状态码。
    • res.sendFile(): 发送文件。
    • res.render(): 渲染视图模板。
    • res.redirect(): 重定向请求。
    • res.set(): 设置响应头。

路由决定了应用程序如何响应特定的端点(URI 路径和 HTTP 方法的组合)。

基本结构是 app.METHOD(PATH, HANDLER),其中 METHOD 是小写的 HTTP 方法(例如,get、post、put、delete),PATH 是 URL 路径(可以包含路由参数和模式),而 HANDLER 是当路由匹配时执行的函数。

让我们扩展我们的示例以处理不同的方法和路径:

const express = require('express');
const app = express();
const port = 3000;
// GET request to the homepage
app.get('/', (req, res) => {
console.log('GET /');
res.send('Hello from GET /');
});
// POST request to the homepage
app.post('/', (req, res) => {
console.log('POST /');
// Note: To access req.body, you need middleware like express.json()
console.log('Request Body:', req.body);
res.send('Hello from POST /');
});
// GET request for listing users
app.get('/users', (req, res) => {
console.log('GET /users');
res.json([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]); // Send JSON data
});
// GET request with a route parameter :userId
app.get('/users/:userId', (req, res) => {
const userId = req.params.userId;
console.log(`GET /users/${userId}`);
res.send(`Details for user ${userId}`);
});
// DELETE request for a specific user
app.delete('/users/:userId', (req, res) => {
const userId = req.params.userId;
console.log(`DELETE /users/${userId}`);
res.status(200).send(`User ${userId} deleted successfully.`); // Send status and message
});
// Catch-all for any other GET request (basic 404)
app.get('*', (req, res) => {
res.status(404).send('Resource not found');
});
app.listen(port, () => {
console.log(`Express app with routing listening at http://localhost:${port}`);
});

保存并运行此代码。您现在可以测试不同的请求:

  • 浏览器/curl GET http://localhost:3000/ -> Hello from GET /
  • 浏览器/curl GET http://localhost:3000/users -> JSON 用户数组
  • 浏览器/curl GET http://localhost:3000/users/123 -> Details for user 123
  • curl -X POST http://localhost:3000/ -> Hello from POST /
  • curl -X DELETE http://localhost:3000/users/456 -> User 456 deleted successfully.
  • 浏览器/curl GET http://localhost:3000/nonexistent -> Resource not found (404 status)

中间件函数可以访问应用程序请求-响应周期中的请求 (req)、响应 (res) 和 next 函数。next 函数将控制权传递给下一个中间件函数。如果当前中间件没有通过发送响应来结束请求周期,则它 必须 调用 next()。

中间件可用于:

  • 执行任何代码。
  • 修改请求和响应对象。
  • 结束请求-响应周期。
  • 调用堆栈中的下一个中间件。

中间件类型:

  • 应用级别 (Application-level): 使用 app.use() 或 app.METHOD() 绑定到 app。
  • 路由级别 (Router-level): 绑定到 express.Router() 的实例。
  • 错误处理 (Error-handling): 有 4 个参数 (err, req, res, next)。通常定义在最后。
  • 内置 (Built-in): 例如,express.json()、express.urlencoded()、express.static()。
  • 第三方 (Third-party): 通过 npm 安装(例如,cors、morgan)。
示例:添加内置和自定义中间件
const express = require('express');
const app = express();
const port = 3000;
// Built-in middleware to parse JSON bodies
app.use(express.json());
// Built-in middleware to parse URL-encoded bodies
app.use(express.urlencoded({ extended: true }));
// Custom application-level middleware: Logger
app.use((req, res, next) => {
const timestamp = new Date().toISOString();
console.log(`${timestamp} - ${req.method} ${req.originalUrl}`);
next(); // Pass control to the next middleware/route handler
});
// Route using the parsed body
app.post('/data', (req, res) => {
console.log('Received data:', req.body);
res.json({ message: 'Data received successfully', data: req.body });
});
app.get('/', (req, res) => {
res.send('Homepage with logging!');
});
// Error-handling middleware (defined last)
app.use((err, req, res, next) => {
console.error('Error caught:', err.stack);
res.status(500).send('Something broke!');
});
app.listen(port, () => {
console.log(`Express app with middleware listening at http://localhost:${port}`);
});

现在,当您发出请求时,您会看到日志消息,并且对带有 JSON 请求体的 /data 发出的 POST 请求将正确解析 req.body。

使用 express.static 内置中间件来提供静态资源,如 HTML、CSS、JavaScript 文件和图像。

在您的项目根目录中创建一个名为 public 的目录。将静态文件放在里面(例如,public/index.html、public/css/style.css、public/images/logo.png)。

将中间件添加到您的 app.js(通常在文件顶部附近):

// Serve static files from the 'public' directory
app.use(express.static('public'));

现在,如果您运行该应用,对 public 目录内的文件发出的请求将自动被提供:

  • http://localhost:3000/index.html -> 提供 public/index.html
  • http://localhost:3000/css/style.css -> 提供 public/css/style.css
  • http://localhost:3000/images/logo.png -> 提供 public/images/logo.png

您可以将静态目录挂载到特定的路径前缀上:app.use('/static', express.static('public'));。然后文件将通过 http://localhost:3000/static/index.html 访问。

让我们创建一个简单的 HTML 表单并处理其提交。

创建 public/form.html:

<!DOCTYPE html>
<html>
<head><title>Form Example</title></head>
<body>
<h2>GET Form</h2>
<form action="/process-get" method="GET">
First Name: <input type="text" name="first_name"><br>
Last Name: <input type="text" name="last_name"><br>
<input type="submit" value="Submit GET">
</form>
<hr>
<h2>POST Form</h2>
<form action="/process-post" method="POST">
First Name: <input type="text" name="first_name"><br>
Last Name: <input type="text" name="last_name"><br>
<input type="submit" value="Submit POST">
</form>
</body>
</html>

更新 app.js 以处理这些表单提交:

const express = require('express');
const app = express();
const port = 3000;
// Middleware for static files and body parsing
app.use(express.static('public'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// --- Routes ---
app.get('/', (req, res) => {
res.redirect('/form.html'); // Redirect root to the form
});
// Handle GET request from form
app.get('/process-get', (req, res) => {
// Data is in the query string for GET requests
const response = {
first_name: req.query.first_name,
last_name: req.query.last_name
};
console.log('GET Request Query:', response);
res.json(response);
});
// Handle POST request from form
app.post('/process-post', (req, res) => {
// Data is in the request body for POST requests (needs urlencoded middleware)
const response = {
first_name: req.body.first_name,
last_name: req.body.last_name
};
console.log('POST Request Body:', response);
res.json(response);
});
// --- Server Start ---
app.listen(port, () => {
console.log(`Express form example listening at http://localhost:${port}`);
});

运行 app.js 并访问 http://localhost:3000/。您将看到表单。提交任一表单都将把数据发送到相应的路由,路由将解析数据(GET 请求从 req.query,POST 请求从 req.body),并将其作为 JSON 返回。

处理文件上传需要 multipart/form-data 编码,这是 Express 的内置解析器无法处理的。通常使用 multer 中间件来处理此事。

安装 multer: $ npm install multer

创建 public/upload.html:

<!DOCTYPE html>
<html>
<head><title>File Upload</title></head>
<body>
<h3>File Upload:</h3>
<form action="/upload-file" method="POST" enctype="multipart/form-data">
Select file: <input type="file" name="myFile"><br>
<input type="submit" value="Upload File">
</form>
</body>
</html>

更新 app.js:

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();
const port = 3000;
// Ensure 'uploads' directory exists
const uploadDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadDir)){
fs.mkdirSync(uploadDir);
}
// Configure Multer
// Store files in the 'uploads/' directory with their original names
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, uploadDir); // Use the absolute path
},
filename: function (req, file, cb) {
// Use original name - caution: potential filename collisions/security issues
// Consider adding unique IDs or timestamps for production
cb(null, file.originalname);
}
});
const upload = multer({ storage: storage });
// Middleware
app.use(express.static('public'));
// Note: Don't use express.json() or express.urlencoded() on the upload route if using multer
// Routes
app.get('/', (req, res) => {
res.redirect('/upload.html');
});
// Handle file upload POST request
// 'myFile' must match the 'name' attribute of the <input type="file"> tag
app.post('/upload-file', upload.single('myFile'), (req, res, next) => {
// req.file is the 'myFile' file
// req.body will hold the text fields, if there were any
if (!req.file) {
return res.status(400).send('No file uploaded.');
}
console.log('File uploaded successfully:');
console.log('Original Name:', req.file.originalname);
console.log('Stored Path:', req.file.path); // Absolute path where stored
console.log('Size:', req.file.size);
console.log('MIME Type:', req.file.mimetype);
const response = {
message: 'File uploaded successfully!',
filename: req.file.originalname,
path: req.file.path,
size: req.file.size
};
res.json(response);
}, (error, req, res, next) => {
// Handle potential multer errors
res.status(400).send({ error: error.message });
});
// Server Start
app.listen(port, () => {
console.log(`Express file upload example listening at http://localhost:${port}`);
});

运行应用,访问 http://localhost:3000/,选择一个文件,然后上传。文件将保存到 uploads 目录(如果不存在则创建)中,该目录位于您的项目根目录,您将收到一个包含文件详细信息的 JSON 响应。

要解析和管理 HTTP cookies,您通常需要 cookie-parser 中间件。

安装: $ npm install cookie-parser

示例:

const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
const port = 3000;
// Use cookie-parser middleware
app.use(cookieParser());
app.get('/', (req, res) => {
// Access cookies from the request object
console.log('Cookies received: ', req.cookies);
// Set a cookie
res.cookie('myCookie', 'someValue', { maxAge: 900000, httpOnly: true }); // Expires in 15 mins
res.cookie('anotherCookie', 'moreData');
res.send('Hello! Check your browser cookies and server console.');
});
app.get('/clear', (req, res) => {
// Clear a cookie
res.clearCookie('myCookie');
res.send('myCookie cleared.');
});
app.listen(port, () => {
console.log(`Express cookie example listening at http://localhost:${port}`);
});

运行此应用,访问 http://localhost:3000/。检查您的浏览器开发者工具(应用/存储 标签页),查看正在设置的 cookies。服务器控制台将记录浏览器在后续请求中发送的任何 cookies。