从零打造个人待办事项管理系统:Node.js + Express + MongoDB 全栈实践
折
折腾侠
2026/03/28 发布
10约 11 分钟1487 字 / 1575 词00
从零打造个人待办事项管理系统:Node.js + Express + MongoDB 全栈实践
项目概述
在本文中,我将带你从零开始构建一个功能完整的个人待办事项管理系统(Todo List Application)。这个项目不仅适合初学者学习全栈开发,也能帮助有经验的开发者巩固 Node.js 生态系统的核心技能。通过完成这个项目,你将掌握后端 API 设计、数据库建模、RESTful 接口实现以及项目结构化组织等关键技能。
项目功能说明
核心功能
-
任务管理
- 创建新任务(支持标题、描述、优先级、截止日期)
- 查看任务列表(支持分页、筛选、排序)
- 更新任务状态(待办/进行中/已完成)
- 删除任务(支持软删除)
- 批量操作(批量完成、批量删除)
-
任务分类
- 自定义分类标签(工作、学习、生活、购物等)
- 按分类筛选任务
- 分类统计(每类任务数量)
-
优先级系统
- 四级优先级(紧急重要、重要不紧急、紧急不重要、普通)
- 优先级颜色标识
- 按优先级排序
-
数据统计
- 今日任务概览
- 本周完成情况统计
- 任务完成趋势图表数据
-
用户认证
- JWT Token 认证
- 用户注册与登录
- 密码加密存储
扩展功能(可选)
- 任务提醒(邮件/短信通知)
- 任务分享与协作
- 子任务支持
- 附件上传
- 移动端适配
技术栈选择
后端技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Node.js | 18.x LTS | 运行时环境 |
| Express | 4.x | Web 框架 |
| MongoDB | 6.x | 数据库 |
| Mongoose | 7.x | ODM 库 |
| JWT | 9.x | 身份认证 |
| Bcrypt | 5.x | 密码加密 |
| dotenv | 16.x | 环境变量管理 |
开发工具
| 工具 | 用途 |
|---|---|
| nodemon | 开发环境热重载 |
| Jest | 单元测试 |
| ESLint | 代码规范检查 |
| Postman | API 测试 |
技术选型理由
-
Node.js + Express:轻量高效,适合快速构建 RESTful API,生态系统成熟,社区活跃。
-
MongoDB + Mongoose:NoSQL 数据库适合存储非结构化数据,Mongoose 提供强大的 Schema 验证和中间件支持。
-
JWT 认证:无状态认证方案,适合前后端分离架构,支持跨域请求。
项目结构设计
todo-app/
├── src/
│ ├── config/
│ │ ├── database.js # 数据库连接配置
│ │ └── index.js # 配置中心
│ ├── controllers/
│ │ ├── authController.js # 认证控制器
│ │ ├── todoController.js # 任务控制器
│ │ └── statsController.js # 统计控制器
│ ├── middleware/
│ │ ├── auth.js # JWT 认证中间件
│ │ ├── validation.js # 请求验证中间件
│ │ └── errorHandler.js # 全局错误处理
│ ├── models/
│ │ ├── User.js # 用户模型
│ │ ├── Todo.js # 任务模型
│ │ └── Category.js # 分类模型
│ ├── routes/
│ │ ├── index.js # 路由汇总
│ │ ├── auth.js # 认证路由
│ │ ├── todos.js # 任务路由
│ │ └── stats.js # 统计路由
│ ├── utils/
│ │ ├── logger.js # 日志工具
│ │ └── helpers.js # 辅助函数
│ ├── validators/
│ │ ├── todoValidator.js # 任务验证规则
│ │ └── userValidator.js # 用户验证规则
│ └── app.js # 应用入口
├── tests/
│ ├── controllers/
│ ├── middleware/
│ └── models/
├── .env # 环境变量
├── .env.example # 环境变量示例
├── .gitignore
├── package.json
├── README.md
└── server.js # 服务器启动入口
核心代码实现
1. 数据库连接配置
JavaScript
// src/config/database.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log(`MongoDB 连接成功:${conn.connection.host}`);
} catch (error) {
console.error(`数据库连接失败:${error.message}`);
process.exit(1);
}
};
module.exports = connectDB;
2. 用户模型设计
JavaScript
// src/models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, '用户名不能为空'],
unique: true,
trim: true,
minlength: 3,
maxlength: 30
},
email: {
type: String,
required: [true, '邮箱不能为空'],
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, '请输入有效的邮箱地址']
},
password: {
type: String,
required: [true, '密码不能为空'],
minlength: 6,
select: false
},
avatar: {
type: String,
default: ''
},
createdAt: {
type: Date,
default: Date.now
}
});
// 密码加密中间件
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) {
return next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// 密码比对方法
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
module.exports = mongoose.model('User', userSchema);
3. 任务模型设计
JavaScript
// src/models/Todo.js
const mongoose = require('mongoose');
const todoSchema = new mongoose.Schema({
title: {
type: String,
required: [true, '任务标题不能为空'],
trim: true,
maxlength: 100
},
description: {
type: String,
trim: true,
maxlength: 500
},
status: {
type: String,
enum: ['pending', 'in-progress', 'completed'],
default: 'pending'
},
priority: {
type: String,
enum: ['urgent-important', 'important', 'urgent', 'normal'],
default: 'normal'
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category'
},
dueDate: {
type: Date
},
completedAt: {
type: Date
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
isDeleted: {
type: Boolean,
default: false
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
// 更新时自动更新时间戳
todoSchema.pre('save', function(next) {
this.updatedAt = Date.now();
next();
});
// 完成时记录完成时间
todoSchema.pre('save', function(next) {
if (this.isModified('status') && this.status === 'completed') {
this.completedAt = Date.now();
}
next();
});
// 索引优化查询性能
todoSchema.index({ user: 1, status: 1 });
todoSchema.index({ user: 1, dueDate: 1 });
todoSchema.index({ user: 1, isDeleted: 1 });
module.exports = mongoose.model('Todo', todoSchema);
4. JWT 认证中间件
JavaScript
// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const protect = async (req, res, next) => {
let token;
// 从 Authorization header 获取 token
if (req.headers.authorization &&
req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return res.status(401).json({
success: false,
message: '未授权访问,请先登录'
});
}
try {
// 验证 token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 获取用户信息(排除密码字段)
req.user = await User.findById(decoded.id).select('-password');
if (!req.user) {
return res.status(401).json({
success: false,
message: '用户不存在'
});
}
next();
} catch (error) {
return res.status(401).json({
success: false,
message: 'Token 无效或已过期'
});
}
};
module.exports = { protect };
5. 任务控制器实现
JavaScript
// src/controllers/todoController.js
const Todo = require('../models/Todo');
// @desc 获取任务列表
// @route GET /api/todos
// @access Private
const getTodos = async (req, res, next) => {
try {
const {
status,
priority,
category,
page = 1,
limit = 10,
sortBy = 'createdAt',
order = 'desc'
} = req.query;
// 构建查询条件
const query = {
user: req.user._id,
isDeleted: false
};
if (status) query.status = status;
if (priority) query.priority = priority;
if (category) query.category = category;
// 执行分页查询
const todos = await Todo.find(query)
.populate('category', 'name color')
.sort({ [sortBy]: order })
.limit(limit * 1)
.skip((page - 1) * limit);
const count = await Todo.countDocuments(query);
res.status(200).json({
success: true,
data: todos,
pagination: {
currentPage: page,
totalPages: Math.ceil(count / limit),
totalItems: count,
itemsPerPage: limit
}
});
} catch (error) {
next(error);
}
};
// @desc 创建新任务
// @route POST /api/todos
// @access Private
const createTodo = async (req, res, next) => {
try {
const { title, description, priority, category, dueDate } = req.body;
const todo = await Todo.create({
title,
description,
priority,
category,
dueDate,
user: req.user._id
});
res.status(201).json({
success: true,
data: todo
});
} catch (error) {
next(error);
}
};
// @desc 更新任务
// @route PUT /api/todos/:id
// @access Private
const updateTodo = async (req, res, next) => {
try {
const todo = await Todo.findOneAndUpdate(
{ _id: req.params.id, user: req.user._id, isDeleted: false },
req.body,
{ new: true, runValidators: true }
);
if (!todo) {
return res.status(404).json({
success: false,
message: '任务不存在'
});
}
res.status(200).json({
success: true,
data: todo
});
} catch (error) {
next(error);
}
};
// @desc 删除任务(软删除)
// @route DELETE /api/todos/:id
// @access Private
const deleteTodo = async (req, res, next) => {
try {
const todo = await Todo.findOneAndUpdate(
{ _id: req.params.id, user: req.user._id },
{ isDeleted: true },
{ new: true }
);
if (!todo) {
return res.status(404).json({
success: false,
message: '任务不存在'
});
}
res.status(200).json({
success: true,
message: '任务已删除'
});
} catch (error) {
next(error);
}
};
module.exports = {
getTodos,
createTodo,
updateTodo,
deleteTodo
};
6. 路由配置
JavaScript
// src/routes/todos.js
const express = require('express');
const router = express.Router();
const { protect } = require('../middleware/auth');
const {
getTodos,
createTodo,
updateTodo,
deleteTodo
} = require('../controllers/todoController');
const { validateTodo } = require('../validators/todoValidator');
// 所有路由都需要认证
router.use(protect);
router.route('/')
.get(getTodos)
.post(validateTodo, createTodo);
router.route('/:id')
.put(validateTodo, updateTodo)
.delete(deleteTodo);
module.exports = router;
7. 应用入口文件
JavaScript
// src/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const routes = require('./routes');
const errorHandler = require('./middleware/errorHandler');
const app = express();
// 安全中间件
app.use(helmet());
// CORS 配置
app.use(cors({
origin: process.env.FRONTEND_URL || '*',
credentials: true
}));
// 请求限流
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100 // 每个 IP 最多 100 个请求
});
app.use('/api', limiter);
// 解析 JSON 请求体
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// API 路由
app.use('/api', routes);
// 健康检查
app.get('/health', (req, res) => {
res.status(200).json({
success: true,
message: '服务运行正常',
timestamp: new Date().toISOString()
});
});
// 全局错误处理
app.use(errorHandler);
// 404 处理
app.use((req, res) => {
res.status(404).json({
success: false,
message: '接口不存在'
});
});
module.exports = app;
8. 服务器启动入口
JavaScript
// server.js
require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/database');
const PORT = process.env.PORT || 3000;
// 连接数据库
connectDB();
// 启动服务器
const server = app.listen(PORT, () => {
console.log(`服务器运行在端口 ${PORT}`);
console.log(`环境:${process.env.NODE_ENV}`);
});
// 处理未捕获的异常
process.on('unhandledRejection', (err) => {
console.error(`错误:${err.message}`);
server.close(() => process.exit(1));
});
module.exports = server;
运行步骤
第一步:环境准备
-
安装 Node.js(推荐 18.x LTS 版本)
Bash# 使用 nvm 安装 nvm install 18 nvm use 18 -
安装 MongoDB
- macOS: INLINE_CODE_0
- Ubuntu: INLINE_CODE_1
- Windows: 下载安装包安装
- 或使用 Docker: INLINE_CODE_2
第二步:项目初始化
Bash
# 创建项目目录
mkdir todo-app
cd todo-app
# 初始化 npm 项目
npm init -y
# 安装生产依赖
npm install express mongoose dotenv bcryptjs jsonwebtoken cors helmet express-rate-limit
# 安装开发依赖
npm install -D nodemon jest eslint
第三步:配置文件
创建 INLINE_CODE_3 文件:
.env
# 服务器配置
PORT=3000
NODE_ENV=development
# 数据库配置
MONGODB_URI=mongodb://localhost:27017/todo-app
# JWT 配置
JWT_SECRET=your-super-secret-jwt-key-change-in-production
JWT_EXPIRE=7d
# 前端地址(CORS)
FRONTEND_URL=http://localhost:3001
第四步:创建项目结构
按照上方项目结构设计创建所有目录和文件,将核心代码复制到对应文件中。
第五步:启动开发服务器
Bash
# 开发模式(支持热重载)
npm run dev
# 生产模式
npm start
第六步:API 测试
使用 Postman 或 curl 测试 API:
Bash
# 用户注册
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"testuser","email":"test@example.com","password":"123456"}'
# 用户登录
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"123456"}'
# 创建任务(使用登录返回的 token)
curl -X POST http://localhost:3000/api/todos \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"title":"学习 Node.js","description":"完成教程","priority":"important"}'
# 获取任务列表
curl -X GET http://localhost:3000/api/todos \
-H "Authorization: Bearer YOUR_TOKEN"
项目优化建议
性能优化
- 数据库索引:为常用查询字段创建索引
- 请求缓存:使用 Redis 缓存频繁查询
- 分页优化:大数据量时使用游标分页
- 连接池:配置 MongoDB 连接池参数
安全加固
- 输入验证:使用 Joi 或 express-validator 进行严格验证
- XSS 防护:对用户输入进行转义
- CSRF 防护:添加 CSRF Token
- 敏感信息:使用环境变量管理密钥
代码质量
- 单元测试:使用 Jest 编写测试用例,覆盖率达到 80% 以上
- 代码规范:配置 ESLint + Prettier
- Git 规范:使用 Conventional Commits
- CI/CD:配置 GitHub Actions 自动化测试和部署
总结
通过这个项目,你掌握了:
- ✅ Express 框架搭建 RESTful API
- ✅ MongoDB 数据库设计与 Mongoose ODM 使用
- ✅ JWT 身份认证实现
- ✅ 中间件开发与使用
- ✅ 项目结构化组织
- ✅ 错误处理与日志记录
- ✅ 环境变量配置
- ✅ API 测试方法
这个项目可以作为你简历上的亮点,也可以作为学习更复杂系统的起点。建议你在此基础上继续扩展功能,如添加前端界面、实现实时通知、支持文件上传等,逐步打造一个完整的全栈应用。
下一步学习建议:
- 使用 React/Vue 构建前端界面
- 添加 WebSocket 实现实时更新
- 部署到云服务器(阿里云/腾讯云)
- 配置 Docker 容器化部署
- 添加 CI/CD 自动化流程
祝你在 Node.js 全栈开发的道路上越走越远!