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 分页与过滤

  • 分页

    • 使用 pagelimit 查询参数
    • 示例:/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);
});

代码解析:

  1. 项目结构:使用了模块化的项目结构,将代码分为模型、路由、中间件等部分

  2. RESTful API 设计:遵循 REST 原则,使用标准的 HTTP 方法和 URL 设计

  3. 数据库集成:使用 Mongoose ODM 操作 MongoDB,实现了用户、产品和订单的 CRUD 操作

  4. 认证与授权:使用 JWT 实现认证,基于角色的访问控制实现授权

  5. 错误处理:实现了统一的错误处理中间件,返回标准化的错误响应

  6. 分页与过滤:支持分页、排序、过滤等功能,提高 API 的灵活性

  7. 数据验证:使用 Mongoose 的验证机制和自定义验证,确保数据的合法性

  8. 事务处理:在订单创建和取消时,实现了库存的增减,确保数据的一致性

运行方法:

  1. 安装依赖:npm install express mongoose body-parser cors bcrypt jsonwebtoken dotenv
  2. 创建项目结构,将上述代码保存到对应文件
  3. 创建 .env 文件,配置 MongoDB 连接信息、JWT 密钥等
  4. 启动 MongoDB 服务
  5. 启动应用:node app.js
  6. 使用 API 测试工具(如 Postman)测试各个接口

学习目标

通过本集的学习,你应该能够:

  1. 理解 REST 架构的核心概念和原则
  2. 掌握 RESTful API 的设计规范,包括 URL 设计、HTTP 方法使用和状态码管理
  3. 学会使用 Express 框架实现 RESTful API
  4. 理解请求/响应格式的设计和实现
  5. 掌握错误处理和异常管理的方法
  6. 学会实现分页、过滤、排序等高级功能
  7. 理解 API 版本控制的方法和应用场景
  8. 实现一个完整的 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 应用。

« 上一篇 Node.js 认证与授权 下一篇 » Node.js GraphQL 基础