Node.js RESTful API 设计
章节概述
REST(Representational State Transfer)是一种软件架构风格,它定义了一组设计原则,用于创建网络服务。RESTful API 是基于 REST 原则设计的 API,它使用 HTTP 方法和 URL 来表示资源和操作。在 Node.js 应用中,我们通常使用 Express 等框架来实现 RESTful API。本集将详细介绍 RESTful API 的设计原则和实现方法,包括 URL 设计、HTTP 方法使用、状态码管理、请求/响应格式和错误处理等,并通过一个完整的 RESTful API 案例展示如何在 Node.js 中实现这些设计原则。
核心知识点讲解
1. REST 架构简介
REST 是由 Roy Fielding 在 2000 年的博士论文中提出的一种软件架构风格,它的核心思想是将网络上的资源视为一种状态,通过 HTTP 方法来操作这些资源。
1.1 REST 的核心概念
- 资源(Resource):网络上的任何实体,如用户、商品、订单等
- 表示(Representation):资源的表现形式,如 JSON、XML 等
- 状态转移(State Transfer):通过 HTTP 方法来改变资源的状态
- 无状态(Stateless):服务器不存储客户端的状态,每次请求都包含完整的信息
- 缓存(Cache):支持缓存机制,提高性能
- 分层系统(Layered System):系统由多个层次组成,每层只与相邻层交互
- 统一接口(Uniform Interface):使用统一的接口来操作资源
1.2 REST 的优势
- 简单性:使用标准的 HTTP 方法和 URL,易于理解和实现
- 可扩展性:无状态设计,便于水平扩展
- 互操作性:基于标准的 HTTP 协议,支持不同系统间的交互
- 可缓存性:支持缓存机制,提高性能
- 灵活性:资源表示形式多样,可根据需要选择
2. RESTful API 设计原则
2.1 资源命名
使用名词:URL 应该使用名词来表示资源,而不是动词
- 正确:
/users、/products - 错误:
/getUsers、/createProduct
- 正确:
使用复数:表示资源集合时,使用复数形式
- 正确:
/users、/products - 错误:
/user、/product
- 正确:
使用嵌套:表示资源之间的关系时,使用嵌套 URL
- 正确:
/users/123/orders、/products/456/reviews - 错误:
/userOrders/123、/productReviews/456
- 正确:
使用小写:URL 应该使用小写字母,避免使用大写
- 正确:
/users、/products - 错误:
/Users、/Products
- 正确:
使用连字符:当资源名称由多个单词组成时,使用连字符分隔
- 正确:
/user-profiles、/product-categories - 错误:
/userProfiles、/productCategories
- 正确:
2.2 HTTP 方法使用
| HTTP 方法 | 描述 | 幂等性 | 安全性 |
|---|---|---|---|
| GET | 获取资源 | 是 | 是 |
| POST | 创建资源 | 否 | 否 |
| PUT | 更新资源 | 是 | 否 |
| PATCH | 部分更新资源 | 是 | 否 |
| DELETE | 删除资源 | 是 | 否 |
| HEAD | 获取资源头部信息 | 是 | 是 |
| OPTIONS | 获取资源支持的方法 | 是 | 是 |
说明:
- 幂等性:多次执行相同的操作,结果相同
- 安全性:不会改变服务器的状态
2.3 状态码管理
| 状态码范围 | 描述 | 示例 |
|---|---|---|
| 100-199 | 信息性状态码 | 100 Continue、101 Switching Protocols |
| 200-299 | 成功状态码 | 200 OK、201 Created、204 No Content |
| 300-399 | 重定向状态码 | 301 Moved Permanently、302 Found、304 Not Modified |
| 400-499 | 客户端错误状态码 | 400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found |
| 500-599 | 服务器错误状态码 | 500 Internal Server Error、501 Not Implemented、503 Service Unavailable |
2.4 请求/响应格式
请求格式:
- 使用 JSON 作为请求体格式
- 设置
Content-Type: application/json头部 - 对于查询参数,使用 URL 查询字符串格式
响应格式:
- 使用 JSON 作为响应体格式
- 设置
Content-Type: application/json头部 - 包含适当的状态码
- 对于错误响应,包含错误信息和错误码
2.5 错误处理
统一错误格式:
{ "success": false, "error": { "code": "INVALID_REQUEST", "message": "请求参数无效" } }客户端错误:
- 400 Bad Request:请求参数无效
- 401 Unauthorized:未授权,需要登录
- 403 Forbidden:禁止访问,权限不足
- 404 Not Found:资源不存在
- 405 Method Not Allowed:方法不允许
- 406 Not Acceptable:无法返回请求的格式
- 409 Conflict:资源冲突
服务器错误:
- 500 Internal Server Error:服务器内部错误
- 501 Not Implemented:方法未实现
- 503 Service Unavailable:服务不可用
2.6 版本控制
URL 版本控制:在 URL 中包含版本号
- 示例:
/v1/users、/v2/products
- 示例:
头部版本控制:在请求头部中包含版本号
- 示例:
Accept: application/vnd.example.v1+json
- 示例:
查询参数版本控制:在查询参数中包含版本号
- 示例:
/users?version=1、/products?version=2
- 示例:
2.7 分页与过滤
分页:
- 使用
page和limit查询参数 - 示例:
/users?page=1&limit=10 - 响应中包含总条数、总页数等信息
- 使用
过滤:
- 使用查询参数进行过滤
- 示例:
/users?status=active&role=admin
排序:
- 使用
sort查询参数 - 示例:
/users?sort=createdAt:desc
- 使用
3. RESTful API 实现
3.1 基础设置
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
// 中间件
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
// 连接数据库
mongoose.connect('mongodb://localhost:27017/restful-api', {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => {
console.log('成功连接到 MongoDB');
}).catch((error) => {
console.error('连接 MongoDB 失败:', error);
});
// 启动服务器
const port = 3000;
app.listen(port, () => {
console.log(`服务器已启动,监听端口 ${port}`);
});3.2 模型定义
const mongoose = require('mongoose');
// 用户模型
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
role: {
type: String,
enum: ['admin', 'user'],
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
// 产品模型
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
category: {
type: String,
required: true
},
stock: {
type: Number,
default: 0
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', userSchema);
const Product = mongoose.model('Product', productSchema);
module.exports = { User, Product };3.3 路由设计
const express = require('express');
const router = express.Router();
const { User, Product } = require('../models');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
// 认证中间件
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: '未提供令牌' } });
}
jwt.verify(token, 'your-secret-key', (err, user) => {
if (err) {
return res.status(403).json({ success: false, error: { code: 'FORBIDDEN', message: '令牌无效' } });
}
req.user = user;
next();
});
}
// 管理员权限中间件
function requireAdmin(req, res, next) {
if (req.user.role !== 'admin') {
return res.status(403).json({ success: false, error: { code: 'FORBIDDEN', message: '需要管理员权限' } });
}
next();
}
// 用户路由
// 注册
router.post('/users', async (req, res) => {
try {
const { name, email, password } = req.body;
// 检查邮箱是否已存在
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(409).json({ success: false, error: { code: 'CONFLICT', message: '邮箱已存在' } });
}
// 哈希密码
const hashedPassword = await bcrypt.hash(password, 10);
// 创建用户
const user = new User({
name,
email,
password: hashedPassword
});
await user.save();
// 生成令牌
const token = jwt.sign({ id: user._id, role: user.role }, 'your-secret-key');
res.status(201).json({
success: true,
data: {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
token
}
});
} catch (error) {
console.error('注册失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 登录
router.post('/users/login', async (req, res) => {
try {
const { email, password } = req.body;
// 查找用户
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: '邮箱或密码错误' } });
}
// 验证密码
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: '邮箱或密码错误' } });
}
// 生成令牌
const token = jwt.sign({ id: user._id, role: user.role }, 'your-secret-key');
res.json({
success: true,
data: {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
token
}
});
} catch (error) {
console.error('登录失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 获取用户列表
router.get('/users', authenticateToken, requireAdmin, async (req, res) => {
try {
const { page = 1, limit = 10, sort = 'createdAt:desc' } = req.query;
const [sortField, sortOrder] = sort.split(':');
const users = await User.find()
.select('-password')
.sort({ [sortField]: sortOrder === 'asc' ? 1 : -1 })
.skip((page - 1) * limit)
.limit(parseInt(limit));
const total = await User.countDocuments();
res.json({
success: true,
data: {
users,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
}
});
} catch (error) {
console.error('获取用户列表失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 获取用户详情
router.get('/users/:id', authenticateToken, async (req, res) => {
try {
const user = await User.findById(req.params.id).select('-password');
if (!user) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '用户不存在' } });
}
res.json({ success: true, data: user });
} catch (error) {
console.error('获取用户详情失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 更新用户信息
router.put('/users/:id', authenticateToken, async (req, res) => {
try {
const { name, email } = req.body;
// 检查是否是本人或管理员
if (req.user.id !== req.params.id && req.user.role !== 'admin') {
return res.status(403).json({ success: false, error: { code: 'FORBIDDEN', message: '权限不足' } });
}
// 检查邮箱是否已被使用
const existingUser = await User.findOne({ email, _id: { $ne: req.params.id } });
if (existingUser) {
return res.status(409).json({ success: false, error: { code: 'CONFLICT', message: '邮箱已存在' } });
}
// 更新用户
const user = await User.findByIdAndUpdate(
req.params.id,
{ name, email, updatedAt: Date.now() },
{ new: true }
).select('-password');
if (!user) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '用户不存在' } });
}
res.json({ success: true, data: user });
} catch (error) {
console.error('更新用户信息失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 删除用户
router.delete('/users/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '用户不存在' } });
}
res.json({ success: true, message: '用户删除成功' });
} catch (error) {
console.error('删除用户失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 产品路由
// 创建产品
router.post('/products', authenticateToken, requireAdmin, async (req, res) => {
try {
const { name, description, price, category, stock } = req.body;
// 创建产品
const product = new Product({
name,
description,
price,
category,
stock
});
await product.save();
res.status(201).json({ success: true, data: product });
} catch (error) {
console.error('创建产品失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 获取产品列表
router.get('/products', async (req, res) => {
try {
const { page = 1, limit = 10, sort = 'createdAt:desc', category, minPrice, maxPrice } = req.query;
const [sortField, sortOrder] = sort.split(':');
// 构建查询条件
const query = {};
if (category) {
query.category = category;
}
if (minPrice) {
query.price = { ...query.price, $gte: parseFloat(minPrice) };
}
if (maxPrice) {
query.price = { ...query.price, $lte: parseFloat(maxPrice) };
}
const products = await Product.find(query)
.sort({ [sortField]: sortOrder === 'asc' ? 1 : -1 })
.skip((page - 1) * limit)
.limit(parseInt(limit));
const total = await Product.countDocuments(query);
res.json({
success: true,
data: {
products,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
}
});
} catch (error) {
console.error('获取产品列表失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 获取产品详情
router.get('/products/:id', async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '产品不存在' } });
}
res.json({ success: true, data: product });
} catch (error) {
console.error('获取产品详情失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 更新产品
router.put('/products/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
const { name, description, price, category, stock } = req.body;
// 更新产品
const product = await Product.findByIdAndUpdate(
req.params.id,
{ name, description, price, category, stock, updatedAt: Date.now() },
{ new: true }
);
if (!product) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '产品不存在' } });
}
res.json({ success: true, data: product });
} catch (error) {
console.error('更新产品失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 部分更新产品
router.patch('/products/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
const updates = req.body;
updates.updatedAt = Date.now();
// 更新产品
const product = await Product.findByIdAndUpdate(
req.params.id,
updates,
{ new: true }
);
if (!product) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '产品不存在' } });
}
res.json({ success: true, data: product });
} catch (error) {
console.error('更新产品失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 删除产品
router.delete('/products/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
const product = await Product.findByIdAndDelete(req.params.id);
if (!product) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '产品不存在' } });
}
res.json({ success: true, message: '产品删除成功' });
} catch (error) {
console.error('删除产品失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
module.exports = router;3.4 中间件
// 错误处理中间件
function errorHandler(err, req, res, next) {
console.error('错误:', err);
// Mongoose 验证错误
if (err.name === 'ValidationError') {
const errors = Object.values(err.errors).map(error => error.message);
return res.status(400).json({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: '验证失败',
details: errors
}
});
}
// Mongoose 重复键错误
if (err.code === 11000) {
const field = Object.keys(err.keyValue)[0];
return res.status(409).json({
success: false,
error: {
code: 'CONFLICT',
message: `${field} 已存在`
}
});
}
// 默认错误
res.status(500).json({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: '服务器内部错误'
}
});
}
// 404 中间件
function notFoundHandler(req, res, next) {
res.status(404).json({
success: false,
error: {
code: 'NOT_FOUND',
message: '资源不存在'
}
});
}
module.exports = { errorHandler, notFoundHandler };3.5 主应用文件
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cors = require('cors');
const routes = require('./routes');
const { errorHandler, notFoundHandler } = require('./middleware');
// 创建 Express 应用
const app = express();
// 中间件
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
// 健康检查
app.get('/health', (req, res) => {
res.json({ status: 'ok', message: '服务运行正常' });
});
// API 路由
app.use('/api', routes);
// 404 中间件
app.use(notFoundHandler);
// 错误处理中间件
app.use(errorHandler);
// 连接数据库
mongoose.connect('mongodb://localhost:27017/restful-api', {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => {
console.log('成功连接到 MongoDB');
// 启动服务器
const port = 3000;
app.listen(port, () => {
console.log(`服务器已启动,监听端口 ${port}`);
});
}).catch((error) => {
console.error('连接 MongoDB 失败:', error);
process.exit(1);
});实用案例分析
案例:完整的 RESTful API
下面我们将使用 Express、MongoDB 和 JWT 实现一个完整的 RESTful API,支持用户管理和产品管理功能。
项目结构
restful-api/
├── app.js
├── models/
│ └── index.js
├── routes/
│ └── index.js
├── middleware/
│ └── index.js
├── package.json
└── .env安装依赖
npm install express mongoose body-parser cors bcrypt jsonwebtoken dotenv配置文件(.env)
# MongoDB 配置
MONGODB_URI=mongodb://localhost:27017/restful-api
# JWT 配置
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=1h
# 服务器端口
PORT=3000模型定义(models/index.js)
const mongoose = require('mongoose');
// 用户模型
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
role: {
type: String,
enum: ['admin', 'user'],
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
// 产品模型
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
category: {
type: String,
required: true
},
stock: {
type: Number,
default: 0
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
// 订单模型
const orderSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
products: [{
productId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product',
required: true
},
quantity: {
type: Number,
required: true,
default: 1
},
price: {
type: Number,
required: true
}
}],
total: {
type: Number,
required: true
},
status: {
type: String,
enum: ['pending', 'processing', 'completed', 'cancelled'],
default: 'pending'
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', userSchema);
const Product = mongoose.model('Product', productSchema);
const Order = mongoose.model('Order', orderSchema);
module.exports = { User, Product, Order };中间件(middleware/index.js)
const jwt = require('jsonwebtoken');
require('dotenv').config();
// 认证中间件
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: '未提供令牌' } });
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ success: false, error: { code: 'FORBIDDEN', message: '令牌无效' } });
}
req.user = user;
next();
});
}
// 管理员权限中间件
function requireAdmin(req, res, next) {
if (req.user.role !== 'admin') {
return res.status(403).json({ success: false, error: { code: 'FORBIDDEN', message: '需要管理员权限' } });
}
next();
}
// 错误处理中间件
function errorHandler(err, req, res, next) {
console.error('错误:', err);
// Mongoose 验证错误
if (err.name === 'ValidationError') {
const errors = Object.values(err.errors).map(error => error.message);
return res.status(400).json({
success: false,
error: {
code: 'VALIDATION_ERROR',
message: '验证失败',
details: errors
}
});
}
// Mongoose 重复键错误
if (err.code === 11000) {
const field = Object.keys(err.keyValue)[0];
return res.status(409).json({
success: false,
error: {
code: 'CONFLICT',
message: `${field} 已存在`
}
});
}
// 默认错误
res.status(500).json({
success: false,
error: {
code: 'INTERNAL_ERROR',
message: '服务器内部错误'
}
});
}
// 404 中间件
function notFoundHandler(req, res, next) {
res.status(404).json({
success: false,
error: {
code: 'NOT_FOUND',
message: '资源不存在'
}
});
}
module.exports = {
authenticateToken,
requireAdmin,
errorHandler,
notFoundHandler
};路由(routes/index.js)
const express = require('express');
const router = express.Router();
const { User, Product, Order } = require('../models');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { authenticateToken, requireAdmin } = require('../middleware');
require('dotenv').config();
// 用户路由
// 注册
router.post('/users', async (req, res) => {
try {
const { name, email, password } = req.body;
// 检查邮箱是否已存在
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(409).json({ success: false, error: { code: 'CONFLICT', message: '邮箱已存在' } });
}
// 哈希密码
const hashedPassword = await bcrypt.hash(password, 10);
// 创建用户
const user = new User({
name,
email,
password: hashedPassword
});
await user.save();
// 生成令牌
const token = jwt.sign({ id: user._id, role: user.role }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN });
res.status(201).json({
success: true,
data: {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
token
}
});
} catch (error) {
console.error('注册失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 登录
router.post('/users/login', async (req, res) => {
try {
const { email, password } = req.body;
// 查找用户
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: '邮箱或密码错误' } });
}
// 验证密码
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ success: false, error: { code: 'UNAUTHORIZED', message: '邮箱或密码错误' } });
}
// 生成令牌
const token = jwt.sign({ id: user._id, role: user.role }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN });
res.json({
success: true,
data: {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
token
}
});
} catch (error) {
console.error('登录失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 获取用户列表
router.get('/users', authenticateToken, requireAdmin, async (req, res) => {
try {
const { page = 1, limit = 10, sort = 'createdAt:desc' } = req.query;
const [sortField, sortOrder] = sort.split(':');
const users = await User.find()
.select('-password')
.sort({ [sortField]: sortOrder === 'asc' ? 1 : -1 })
.skip((page - 1) * limit)
.limit(parseInt(limit));
const total = await User.countDocuments();
res.json({
success: true,
data: {
users,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
}
});
} catch (error) {
console.error('获取用户列表失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 获取用户详情
router.get('/users/:id', authenticateToken, async (req, res) => {
try {
const user = await User.findById(req.params.id).select('-password');
if (!user) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '用户不存在' } });
}
res.json({ success: true, data: user });
} catch (error) {
console.error('获取用户详情失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 更新用户信息
router.put('/users/:id', authenticateToken, async (req, res) => {
try {
const { name, email } = req.body;
// 检查是否是本人或管理员
if (req.user.id !== req.params.id && req.user.role !== 'admin') {
return res.status(403).json({ success: false, error: { code: 'FORBIDDEN', message: '权限不足' } });
}
// 检查邮箱是否已被使用
const existingUser = await User.findOne({ email, _id: { $ne: req.params.id } });
if (existingUser) {
return res.status(409).json({ success: false, error: { code: 'CONFLICT', message: '邮箱已存在' } });
}
// 更新用户
const user = await User.findByIdAndUpdate(
req.params.id,
{ name, email, updatedAt: Date.now() },
{ new: true }
).select('-password');
if (!user) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '用户不存在' } });
}
res.json({ success: true, data: user });
} catch (error) {
console.error('更新用户信息失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 删除用户
router.delete('/users/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '用户不存在' } });
}
res.json({ success: true, message: '用户删除成功' });
} catch (error) {
console.error('删除用户失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 产品路由
// 创建产品
router.post('/products', authenticateToken, requireAdmin, async (req, res) => {
try {
const { name, description, price, category, stock } = req.body;
// 创建产品
const product = new Product({
name,
description,
price,
category,
stock
});
await product.save();
res.status(201).json({ success: true, data: product });
} catch (error) {
console.error('创建产品失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 获取产品列表
router.get('/products', async (req, res) => {
try {
const { page = 1, limit = 10, sort = 'createdAt:desc', category, minPrice, maxPrice } = req.query;
const [sortField, sortOrder] = sort.split(':');
// 构建查询条件
const query = {};
if (category) {
query.category = category;
}
if (minPrice) {
query.price = { ...query.price, $gte: parseFloat(minPrice) };
}
if (maxPrice) {
query.price = { ...query.price, $lte: parseFloat(maxPrice) };
}
const products = await Product.find(query)
.sort({ [sortField]: sortOrder === 'asc' ? 1 : -1 })
.skip((page - 1) * limit)
.limit(parseInt(limit));
const total = await Product.countDocuments(query);
res.json({
success: true,
data: {
products,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
}
});
} catch (error) {
console.error('获取产品列表失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 获取产品详情
router.get('/products/:id', async (req, res) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '产品不存在' } });
}
res.json({ success: true, data: product });
} catch (error) {
console.error('获取产品详情失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 更新产品
router.put('/products/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
const { name, description, price, category, stock } = req.body;
// 更新产品
const product = await Product.findByIdAndUpdate(
req.params.id,
{ name, description, price, category, stock, updatedAt: Date.now() },
{ new: true }
);
if (!product) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '产品不存在' } });
}
res.json({ success: true, data: product });
} catch (error) {
console.error('更新产品失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 部分更新产品
router.patch('/products/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
const updates = req.body;
updates.updatedAt = Date.now();
// 更新产品
const product = await Product.findByIdAndUpdate(
req.params.id,
updates,
{ new: true }
);
if (!product) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '产品不存在' } });
}
res.json({ success: true, data: product });
} catch (error) {
console.error('更新产品失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 删除产品
router.delete('/products/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
const product = await Product.findByIdAndDelete(req.params.id);
if (!product) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '产品不存在' } });
}
res.json({ success: true, message: '产品删除成功' });
} catch (error) {
console.error('删除产品失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 订单路由
// 创建订单
router.post('/orders', authenticateToken, async (req, res) => {
try {
const { products } = req.body;
// 验证产品
let total = 0;
const orderProducts = [];
for (const item of products) {
const product = await Product.findById(item.productId);
if (!product) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: `产品 ${item.productId} 不存在` } });
}
if (product.stock < item.quantity) {
return res.status(400).json({ success: false, error: { code: 'BAD_REQUEST', message: `产品 ${product.name} 库存不足` } });
}
total += product.price * item.quantity;
orderProducts.push({
productId: product._id,
quantity: item.quantity,
price: product.price
});
// 减少库存
product.stock -= item.quantity;
await product.save();
}
// 创建订单
const order = new Order({
userId: req.user.id,
products: orderProducts,
total
});
await order.save();
res.status(201).json({ success: true, data: order });
} catch (error) {
console.error('创建订单失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 获取订单列表
router.get('/orders', authenticateToken, async (req, res) => {
try {
const { page = 1, limit = 10, sort = 'createdAt:desc', status } = req.query;
const [sortField, sortOrder] = sort.split(':');
// 构建查询条件
const query = {};
if (req.user.role !== 'admin') {
query.userId = req.user.id;
}
if (status) {
query.status = status;
}
const orders = await Order.find(query)
.populate('userId', 'name email')
.populate('products.productId', 'name price')
.sort({ [sortField]: sortOrder === 'asc' ? 1 : -1 })
.skip((page - 1) * limit)
.limit(parseInt(limit));
const total = await Order.countDocuments(query);
res.json({
success: true,
data: {
orders,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
}
});
} catch (error) {
console.error('获取订单列表失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 获取订单详情
router.get('/orders/:id', authenticateToken, async (req, res) => {
try {
const order = await Order.findById(req.params.id)
.populate('userId', 'name email')
.populate('products.productId', 'name price');
if (!order) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '订单不存在' } });
}
// 检查权限
if (req.user.role !== 'admin' && order.userId._id.toString() !== req.user.id) {
return res.status(403).json({ success: false, error: { code: 'FORBIDDEN', message: '权限不足' } });
}
res.json({ success: true, data: order });
} catch (error) {
console.error('获取订单详情失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 更新订单状态
router.patch('/orders/:id', authenticateToken, async (req, res) => {
try {
const { status } = req.body;
// 检查订单
const order = await Order.findById(req.params.id);
if (!order) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '订单不存在' } });
}
// 检查权限
if (req.user.role !== 'admin' && order.userId.toString() !== req.user.id) {
return res.status(403).json({ success: false, error: { code: 'FORBIDDEN', message: '权限不足' } });
}
// 只有管理员可以更新状态
if (req.user.role !== 'admin') {
return res.status(403).json({ success: false, error: { code: 'FORBIDDEN', message: '需要管理员权限' } });
}
// 更新订单
order.status = status;
order.updatedAt = Date.now();
await order.save();
// 如果订单被取消,恢复库存
if (status === 'cancelled') {
for (const item of order.products) {
const product = await Product.findById(item.productId);
if (product) {
product.stock += item.quantity;
await product.save();
}
}
}
res.json({ success: true, data: order });
} catch (error) {
console.error('更新订单失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
// 删除订单
router.delete('/orders/:id', authenticateToken, requireAdmin, async (req, res) => {
try {
const order = await Order.findByIdAndDelete(req.params.id);
if (!order) {
return res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: '订单不存在' } });
}
// 恢复库存
for (const item of order.products) {
const product = await Product.findById(item.productId);
if (product) {
product.stock += item.quantity;
await product.save();
}
}
res.json({ success: true, message: '订单删除成功' });
} catch (error) {
console.error('删除订单失败:', error);
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' } });
}
});
module.exports = router;主应用文件(app.js)
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cors = require('cors');
const routes = require('./routes');
const { errorHandler, notFoundHandler } = require('./middleware');
require('dotenv').config();
// 创建 Express 应用
const app = express();
// 中间件
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
// 健康检查
app.get('/health', (req, res) => {
res.json({ status: 'ok', message: '服务运行正常' });
});
// API 路由
app.use('/api', routes);
// 404 中间件
app.use(notFoundHandler);
// 错误处理中间件
app.use(errorHandler);
// 连接数据库
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => {
console.log('成功连接到 MongoDB');
// 启动服务器
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`服务器已启动,监听端口 ${port}`);
});
}).catch((error) => {
console.error('连接 MongoDB 失败:', error);
process.exit(1);
});代码解析:
项目结构:使用了模块化的项目结构,将代码分为模型、路由、中间件等部分
RESTful API 设计:遵循 REST 原则,使用标准的 HTTP 方法和 URL 设计
数据库集成:使用 Mongoose ODM 操作 MongoDB,实现了用户、产品和订单的 CRUD 操作
认证与授权:使用 JWT 实现认证,基于角色的访问控制实现授权
错误处理:实现了统一的错误处理中间件,返回标准化的错误响应
分页与过滤:支持分页、排序、过滤等功能,提高 API 的灵活性
数据验证:使用 Mongoose 的验证机制和自定义验证,确保数据的合法性
事务处理:在订单创建和取消时,实现了库存的增减,确保数据的一致性
运行方法:
- 安装依赖:
npm install express mongoose body-parser cors bcrypt jsonwebtoken dotenv - 创建项目结构,将上述代码保存到对应文件
- 创建
.env文件,配置 MongoDB 连接信息、JWT 密钥等 - 启动 MongoDB 服务
- 启动应用:
node app.js - 使用 API 测试工具(如 Postman)测试各个接口
学习目标
通过本集的学习,你应该能够:
- 理解 REST 架构的核心概念和原则
- 掌握 RESTful API 的设计规范,包括 URL 设计、HTTP 方法使用和状态码管理
- 学会使用 Express 框架实现 RESTful API
- 理解请求/响应格式的设计和实现
- 掌握错误处理和异常管理的方法
- 学会实现分页、过滤、排序等高级功能
- 理解 API 版本控制的方法和应用场景
- 实现一个完整的 RESTful API,包括用户、产品和订单管理
小结
RESTful API 是一种基于 REST 原则设计的 API,它使用 HTTP 方法和 URL 来表示资源和操作。本集介绍了 RESTful API 的设计原则和实现方法,包括 URL 设计、HTTP 方法使用、状态码管理、请求/响应格式和错误处理等。通过一个完整的 RESTful API 案例,我们展示了如何在 Node.js 中使用 Express、MongoDB 和 JWT 实现这些设计原则,包括用户管理、产品管理和订单管理等功能。
在实际应用中,我们需要根据具体的业务场景和需求来设计 RESTful API,确保 API 的易用性、可扩展性和安全性。同时,我们还需要考虑 API 的性能优化、文档编写和测试等方面,以确保 API 的质量和可靠性。
RESTful API 是现代 Web 应用的重要组成部分,它为前端和后端之间的通信提供了标准化的接口,便于不同系统之间的集成和交互。通过掌握 RESTful API 的设计和实现方法,我们可以开发出更加灵活、高效和可靠的 Web 应用。