RESTful API #
一、项目概述 #
1.1 项目结构 #
text
api-project/
├── config/
│ ├── database.js
│ └── index.js
├── controllers/
│ ├── authController.js
│ ├── userController.js
│ └── postController.js
├── middlewares/
│ ├── auth.js
│ ├── errorHandler.js
│ └── validate.js
├── models/
│ ├── User.js
│ └── Post.js
├── routes/
│ ├── index.js
│ ├── authRoutes.js
│ ├── userRoutes.js
│ └── postRoutes.js
├── services/
│ ├── authService.js
│ └── postService.js
├── utils/
│ ├── apiResponse.js
│ ├── asyncHandler.js
│ └── jwt.js
├── tests/
├── app.js
├── server.js
└── package.json
二、入口文件 #
2.1 app.js #
javascript
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const routes = require('./routes');
const errorHandler = require('./middlewares/errorHandler');
const app = express();
app.use(helmet());
app.use(cors({
origin: process.env.CORS_ORIGIN || '*',
credentials: true
}));
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'));
}
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
});
app.use('/api', limiter);
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true }));
app.use(mongoSanitize());
app.use(xss());
app.use('/api', routes);
app.use(errorHandler);
module.exports = app;
2.2 server.js #
javascript
const dotenv = require('dotenv');
dotenv.config();
const app = require('./app');
const connectDB = require('./config/database');
connectDB();
const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => {
console.log(`服务器运行在端口 ${PORT}`);
});
process.on('unhandledRejection', (err) => {
console.error('未处理的Promise拒绝:', err.message);
server.close(() => process.exit(1));
});
三、数据库配置 #
3.1 config/database.js #
javascript
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.DATABASE_URL, {
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000
});
console.log(`数据库连接成功: ${conn.connection.host}`);
} catch (error) {
console.error('数据库连接失败:', error.message);
process.exit(1);
}
};
module.exports = connectDB;
四、模型定义 #
4.1 models/User.js #
javascript
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, '请输入用户名'],
trim: true,
maxlength: [50, '用户名不能超过50个字符']
},
email: {
type: String,
required: [true, '请输入邮箱'],
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, '请输入有效的邮箱地址']
},
password: {
type: String,
required: [true, '请输入密码'],
minlength: [6, '密码至少6个字符'],
select: false
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
avatar: String,
bio: String
}, {
timestamps: true
});
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
});
userSchema.methods.comparePassword = async function(password) {
return await bcrypt.compare(password, this.password);
};
userSchema.methods.toJSON = function() {
const user = this.toObject();
delete user.password;
return user;
};
module.exports = mongoose.model('User', userSchema);
4.2 models/Post.js #
javascript
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: [true, '请输入标题'],
trim: true,
maxlength: [200, '标题不能超过200个字符']
},
content: {
type: String,
required: [true, '请输入内容']
},
excerpt: {
type: String,
maxlength: [300, '摘要不能超过300个字符']
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
tags: [String],
status: {
type: String,
enum: ['draft', 'published'],
default: 'draft'
},
viewCount: {
type: Number,
default: 0
}
}, {
timestamps: true
});
postSchema.index({ title: 'text', content: 'text' });
module.exports = mongoose.model('Post', postSchema);
五、路由定义 #
5.1 routes/index.js #
javascript
const express = require('express');
const router = express.Router();
const authRoutes = require('./authRoutes');
const userRoutes = require('./userRoutes');
const postRoutes = require('./postRoutes');
router.use('/auth', authRoutes);
router.use('/users', userRoutes);
router.use('/posts', postRoutes);
router.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
module.exports = router;
5.2 routes/authRoutes.js #
javascript
const express = require('express');
const router = express.Router();
const { body } = require('express-validator');
const authController = require('../controllers/authController');
const { validate } = require('../middlewares/validate');
router.post('/register', [
body('name').notEmpty().trim(),
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 6 }),
validate
], authController.register);
router.post('/login', [
body('email').isEmail(),
body('password').notEmpty(),
validate
], authController.login);
router.post('/refresh-token', authController.refreshToken);
module.exports = router;
5.3 routes/postRoutes.js #
javascript
const express = require('express');
const router = express.Router();
const { body, param, query } = require('express-validator');
const postController = require('../controllers/postController');
const { auth, authorize } = require('../middlewares/auth');
const { validate } = require('../middlewares/validate');
router.get('/', [
query('page').optional().isInt({ min: 1 }),
query('limit').optional().isInt({ min: 1, max: 100 }),
validate
], postController.getAll);
router.get('/:id', [
param('id').isMongoId(),
validate
], postController.getById);
router.post('/', auth, [
body('title').notEmpty().trim(),
body('content').notEmpty(),
validate
], postController.create);
router.put('/:id', auth, [
param('id').isMongoId(),
body('title').optional().notEmpty().trim(),
body('content').optional().notEmpty(),
validate
], postController.update);
router.delete('/:id', auth, [
param('id').isMongoId(),
validate
], postController.delete);
module.exports = router;
六、控制器 #
6.1 controllers/postController.js #
javascript
const Post = require('../models/Post');
const asyncHandler = require('../utils/asyncHandler');
const ApiResponse = require('../utils/apiResponse');
exports.getAll = asyncHandler(async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const [posts, total] = await Promise.all([
Post.find({ status: 'published' })
.populate('author', 'name email')
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit),
Post.countDocuments({ status: 'published' })
]);
ApiResponse.paginated(res, posts, total, page, limit);
});
exports.getById = asyncHandler(async (req, res) => {
const post = await Post.findByIdAndUpdate(
req.params.id,
{ $inc: { viewCount: 1 } },
{ new: true }
).populate('author', 'name email');
if (!post) {
return ApiResponse.notFound(res, '文章不存在');
}
ApiResponse.success(res, post);
});
exports.create = asyncHandler(async (req, res) => {
const post = await Post.create({
...req.body,
author: req.user.id
});
ApiResponse.created(res, post, '文章创建成功');
});
exports.update = asyncHandler(async (req, res) => {
let post = await Post.findById(req.params.id);
if (!post) {
return ApiResponse.notFound(res, '文章不存在');
}
if (post.author.toString() !== req.user.id && req.user.role !== 'admin') {
return ApiResponse.forbidden(res, '无权修改此文章');
}
post = await Post.findByIdAndUpdate(
req.params.id,
{ $set: req.body },
{ new: true, runValidators: true }
);
ApiResponse.success(res, post, '文章更新成功');
});
exports.delete = asyncHandler(async (req, res) => {
const post = await Post.findById(req.params.id);
if (!post) {
return ApiResponse.notFound(res, '文章不存在');
}
if (post.author.toString() !== req.user.id && req.user.role !== 'admin') {
return ApiResponse.forbidden(res, '无权删除此文章');
}
await post.deleteOne();
ApiResponse.success(res, null, '文章删除成功');
});
七、工具类 #
7.1 utils/apiResponse.js #
javascript
class ApiResponse {
static success(res, data, message = '成功') {
return res.json({
success: true,
data,
message
});
}
static created(res, data, message = '创建成功') {
return res.status(201).json({
success: true,
data,
message
});
}
static paginated(res, data, total, page, limit) {
return res.json({
success: true,
data,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
});
}
static error(res, message, statusCode = 400) {
return res.status(statusCode).json({
success: false,
error: message
});
}
static notFound(res, message = '资源未找到') {
return this.error(res, message, 404);
}
static unauthorized(res, message = '未授权') {
return this.error(res, message, 401);
}
static forbidden(res, message = '禁止访问') {
return this.error(res, message, 403);
}
}
module.exports = ApiResponse;
7.2 utils/asyncHandler.js #
javascript
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
module.exports = asyncHandler;
八、中间件 #
8.1 middlewares/auth.js #
javascript
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const asyncHandler = require('../utils/asyncHandler');
exports.auth = asyncHandler(async (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: '请先登录' });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.id);
if (!user) {
return res.status(401).json({ error: '用户不存在' });
}
req.user = user;
next();
});
exports.authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: '没有权限' });
}
next();
};
};
8.2 middlewares/errorHandler.js #
javascript
const errorHandler = (err, req, res, next) => {
let statusCode = err.statusCode || 500;
let message = err.message || '服务器错误';
if (err.name === 'ValidationError') {
statusCode = 400;
message = Object.values(err.errors).map(e => e.message).join(', ');
}
if (err.code === 11000) {
statusCode = 400;
message = '数据已存在';
}
if (err.name === 'JsonWebTokenError') {
statusCode = 401;
message = '无效的token';
}
res.status(statusCode).json({
success: false,
error: message,
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
});
};
module.exports = errorHandler;
九、总结 #
RESTful API项目要点:
| 组件 | 说明 |
|---|---|
| 入口文件 | app.js, server.js |
| 路由 | 定义API端点 |
| 控制器 | 处理请求逻辑 |
| 模型 | 数据结构定义 |
| 中间件 | 认证、验证、错误处理 |
| 工具类 | 响应格式、异步处理 |
下一步,让我们学习用户认证系统!
最后更新:2026-03-28