Skip to content

ExpressJS - RESTful API

使用 Express.js 构建现代 RESTful API

Section titled “使用 Express.js 构建现代 RESTful API”

RESTful API(表征状态传输)是一种用于设计网络应用程序的架构风格。它使用无状态、客户端-服务器通信模型,并利用标准 HTTP 方法。对于 Express 应用程序而言,这意味着创建结构良好、直观、可预测且易于客户端应用程序(如移动应用或单页 Web 应用)使用的端点。

一个设计良好的 RESTful API 逻辑地使用 HTTP 动词和基于资源的 URL。以下是一个 movies 资源的标准约定:

方法URI操作描述
GET/api/movies读取检索所有电影的列表。
GET/api/movies/:id读取通过其唯一 ID 检索单个电影。
POST/api/movies创建使用请求体中的数据创建新电影。
PUT/api/movies/:id更新用新数据替换整个现有电影。
PATCH/api/movies/:id更新用新数据部分更新现有电影。
DELETE/api/movies/:id删除通过其唯一 ID 删除电影。

让我们遵循现代最佳实践来构建这个 API。我们将使用内存数组以简化示例,但其结构设计使其可以轻松替换为真实数据库。

我们将组织代码以分离关注点:路由、控制器和数据逻辑。

此文件设置 Express 应用程序、应用全局中间件并挂载我们的 API 路由。请注意,body-parser 不再需要;我们使用内置的 express.json()。

import express from 'express';
import movieRoutes from './movie.routes.js';
const app = express();
// 全局中间件,用于解析 JSON 请求体
app.use(express.json());
// 在 /api/movies 路径上挂载电影路由
app.use('/api/movies', movieRoutes);
// 一个简单的 404 处理器
app.use((req, res, next) => {
res.status(404).json({ message: 'Resource not found' });
});
export default app; // 导出供测试和 server.js 使用

2. 内存数据和路由(movie.routes.js)

Section titled “2. 内存数据和路由(movie.routes.js)”

此文件定义路由并将它们连接到控制器函数。它还保存了我们的临时内存数据存储。

import { Router } from 'express';
import {
getAllMovies,
getMovieById,
createMovie,
updateMovie,
deleteMovie,
} from './movie.controller.js';
const router = Router();
// 内存数据存储(在实际应用中替换为数据库)
export let movies = [
{ id: 1, title: 'Fight Club', year: 1999, rating: 8.8 },
{ id: 2, title: 'Inception', year: 2010, rating: 8.7 },
{ id: 3, title: 'The Dark Knight', year: 2008, rating: 9.0 },
];
router.route('/')
.get(getAllMovies)
.post(createMovie);
router.route('/:id')
.get(getMovieById)
.put(updateMovie)
.delete(deleteMovie);
export default router;

控制器处理请求和响应周期。它们包含每个路由的主要逻辑,保持路由定义清晰。注意:在实际应用中,查找/更新数据的逻辑将位于单独的“服务”或“模型”层中。

import { movies } from './movie.routes.js'; // 注意:这种紧密耦合是为了示例的简洁性
let nextId = 4; // 简单的 ID 增量器
// GET /api/movies
export const getAllMovies = (req, res) => {
res.status(200).json(movies);
};
// GET /api/movies/:id
export const getMovieById = (req, res) => {
const movie = movies.find(m => m.id === parseInt(req.params.id));
if (!movie) {
return res.status(404).json({ message: 'Movie not found' });
}
res.status(200).json(movie);
};
// POST /api/movies
export const createMovie = (req, res) => {
const { title, year, rating } = req.body;
// 基本验证
if (!title || !year || !rating) {
return res.status(400).json({ message: 'Bad Request: Missing required fields' });
}
const newMovie = { id: nextId++, title, year, rating };
movies.push(newMovie);
res.status(201).json({ message: 'New movie created.', location: `/api/movies/${newMovie.id}` });
};
// PUT /api/movies/:id
export const updateMovie = (req, res) => {
const movieIndex = movies.findIndex(m => m.id === parseInt(req.params.id));
if (movieIndex === -1) {
return res.status(404).json({ message: 'Movie not found' });
}
const { title, year, rating } = req.body;
if (!title || !year || !rating) {
return res.status(400).json({ message: 'Bad Request: Missing required fields' });
}
const updatedMovie = { id: parseInt(req.params.id), title, year, rating };
movies[movieIndex] = updatedMovie;
res.status(200).json({ message: `Movie with id ${req.params.id} updated.`, movie: updatedMovie });
};
// DELETE /api/movies/:id
export const deleteMovie = (req, res) => {
const movieIndex = movies.findIndex(m => m.id === parseInt(req.params.id));
if (movieIndex === -1) {
return res.status(404).json({ message: 'Movie not found' });
}
movies.splice(movieIndex, 1);
res.status(204).send(); // 204 No Content 是成功删除的常见响应
};

您可以使用 curl 或 Postman 等工具与您运行的 API 进行交互。

curl -X POST http://localhost:3000/api/movies \
-H "Content-Type: application/json" \
-d '{"title": "The Matrix", "year": 1999, "rating": 8.7}'

响应:

{
"message": "New movie created.",
"location": "/api/movies/4"
}
curl http://localhost:3000/api/movies/3

响应:

{
"id": 3,
"title": "The Dark Knight",
"year": 2008,
"rating": 9.0
}
  • 输入验证:当前的验证是基本的。使用像 joi 或 express-validator 这样的库来创建健壮、可重用的验证模式,这些模式在控制器逻辑运行之前作为中间件应用。
  • 数据库集成:将内存中的 movies 数组替换为持久数据库连接(例如,使用 Mongoose 的 MongoDB,或使用 Sequelize 或 Prisma 等 ORM 的 PostgreSQL)。
  • 集中式错误处理:实现一个全局错误处理中间件,以捕获控制器中的错误(特别是来自 async 数据库操作的错误),并返回一致的、格式化的错误响应。