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。我们将使用内存数组以简化示例,但其结构设计使其可以轻松替换为真实数据库。
我们将组织代码以分离关注点:路由、控制器和数据逻辑。
1. 主应用程序文件(app.js)
Section titled “1. 主应用程序文件(app.js)”此文件设置 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;控制器逻辑(movie.controller.js)
Section titled “控制器逻辑(movie.controller.js)”控制器处理请求和响应周期。它们包含每个路由的主要逻辑,保持路由定义清晰。注意:在实际应用中,查找/更新数据的逻辑将位于单独的“服务”或“模型”层中。
import { movies } from './movie.routes.js'; // 注意:这种紧密耦合是为了示例的简洁性
let nextId = 4; // 简单的 ID 增量器
// GET /api/moviesexport const getAllMovies = (req, res) => { res.status(200).json(movies);};
// GET /api/movies/:idexport 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/moviesexport 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/:idexport 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/:idexport 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 测试 API
Section titled “使用 curl 测试 API”您可以使用 curl 或 Postman 等工具与您运行的 API 进行交互。
创建新电影 (POST)
Section titled “创建新电影 (POST)”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"}获取特定电影 (GET)
Section titled “获取特定电影 (GET)”curl http://localhost:3000/api/movies/3响应:
{ "id": 3, "title": "The Dark Knight", "year": 2008, "rating": 9.0}后续步骤:改进 API
Section titled “后续步骤:改进 API”- 输入验证:当前的验证是基本的。使用像
joi或express-validator这样的库来创建健壮、可重用的验证模式,这些模式在控制器逻辑运行之前作为中间件应用。 - 数据库集成:将内存中的
movies数组替换为持久数据库连接(例如,使用 Mongoose 的 MongoDB,或使用 Sequelize 或 Prisma 等 ORM 的 PostgreSQL)。 - 集中式错误处理:实现一个全局错误处理中间件,以捕获控制器中的错误(特别是来自
async数据库操作的错误),并返回一致的、格式化的错误响应。