Node.js: 任务 API 项目(上)
最后更新:2026-08-26
1. 项目启动:Alice 的第一天
Alice 和团队接到一个新需求——构建团队任务管理 API。第一天,他们决定先搭建项目骨架和用户认证模块。"认证是一切的基础,"Alice 说,"没有身份验证,后面的任务管理就没有意义。"
- 项目初始化与目录结构设计是工程化的第一步
- Mongoose 模型设计决定了数据层的基础
- JWT + bcrypt 组合是 Node.js 认证的主流方案
- 注册/登录 API 是认证模块的两大核心端点
- auth 中间件保护需要身份验证的路由
2. 项目初始化与目录结构
▶ 示例:(1) 初始化 Express 项目
BASH
mkdir task-manager-api && cd task-manager-api
npm init -y
npm install express mongoose bcryptjs jsonwebtoken dotenv cors helmet
npm install --save-dev nodemon
▶ 示例:(2) 目录结构设计
TEXT
📖 仅展示
task-manager-api/
├── src/
│ ├── config/
│ │ └── db.js
│ ├── middleware/
│ │ └── auth.js
│ ├── models/
│ │ ├── User.js
│ │ └── Task.js
│ ├── routes/
│ │ ├── auth.js
│ │ └── tasks.js
│ ├── validators/
│ │ └── authValidator.js
│ └── app.js
├── .env
├── .gitignore
├── package.json
└── server.js
| 目录/文件 | 说明 |
|---|---|
src/config/ |
数据库连接、环境变量等配置 |
src/middleware/ |
认证、错误处理等中间件 |
src/models/ |
Mongoose Schema 与模型定义 |
src/routes/ |
路由模块,按功能拆分 |
src/validators/ |
请求参数验证逻辑 |
src/app.js |
Express 应用主文件 |
server.js |
入口文件,启动服务器 |
▶ 示例:server.js 入口文件
JAVASCRIPT
require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/db');
connectDB();
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
▶ 示例:数据库连接配置
JAVASCRIPT
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log('MongoDB connected');
} catch (err) {
console.error('MongoDB connection error:', err.message);
process.exit(1);
}
};
module.exports = connectDB;
3. Mongoose 模型设计
(1) User 模型
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
username |
String | 是 | 用户名,唯一索引 |
email |
String | 是 | 邮箱,唯一索引 |
password |
String | 是 | bcrypt 哈希后的密码 |
role |
String | 否 | 角色:user(默认)/ admin |
createdAt |
Date | 自动 | 创建时间 |
(2) Task 模型
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
title |
String | 是 | 任务标题 |
description |
String | 否 | 任务详情 |
status |
String | 否 | pending(默认)/ in-progress/ completed |
priority |
String | 否 | low(默认)/ medium/ high |
assignedTo |
ObjectId | 是 | 指派给的用户,关联 User |
dueDate |
Date | 否 | 截止日期 |
createdAt |
Date | 自动 | 创建时间 |
updatedAt |
Date | 自动 | 更新时间 |
▶ 示例:User 模型定义
JAVASCRIPT
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
password: { type: String, required: true, minlength: 6 },
role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
userSchema.methods.comparePassword = function (candidate) {
return bcrypt.compare(candidate, this.password);
};
module.exports = mongoose.model('User', userSchema);
▶ 示例:Task 模型定义
JAVASCRIPT
const mongoose = require('mongoose');
const taskSchema = new mongoose.Schema({
title: { type: String, required: true, trim: true },
description: { type: String, default: '' },
status: { type: String, enum: ['pending', 'in-progress', 'completed'], default: 'pending' },
priority: { type: String, enum: ['low', 'medium', 'high'], default: 'low' },
assignedTo: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
dueDate: { type: Date }
}, { timestamps: true });
module.exports = mongoose.model('Task', taskSchema);
4. 用户认证模块
▶ 示例:(1) JWT 认证原理
graph TD
A[用户注册/登录] --> B[服务器验证凭据]
B --> C[生成 JWT Token]
C --> D[返回 Token 给客户端]
D --> E[客户端携带 Token 请求]
E --> F[auth 中间件验证 Token]
F -->|有效| G[放行到路由处理器]
F -->|无效| H[返回 401 错误]
(2) 认证 API 端点设计
| 方法 | 路径 | 说明 | 是否需要认证 |
|---|---|---|---|
| POST | /api/auth/register |
用户注册 | 否 |
| POST | /api/auth/login |
用户登录 | 否 |
| GET | /api/auth/me |
获取当前用户 | 是 |
▶ 示例:注册与登录路由
JAVASCRIPT
const router = require('express').Router();
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const generateToken = (id) => jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '7d' });
router.post('/register', async (req, res, next) => {
try {
const { username, email, password } = req.body;
const user = await User.create({ username, email, password });
res.status(201).json({ token: generateToken(user._id), user: { id: user._id, username, email, role: user.role } });
} catch (err) {
next(err);
}
});
router.post('/login', async (req, res, next) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}
res.json({ token: generateToken(user._id), user: { id: user._id, username: user.username, email, role: user.role } });
} catch (err) {
next(err);
}
});
module.exports = router;
▶ 示例:auth 中间件
JAVASCRIPT
const jwt = require('jsonwebtoken');
const User = require('../models/User');
module.exports = async (req, res, next) => {
const header = req.headers.authorization;
if (!header || !header.startsWith('Bearer ')) {
return res.status(401).json({ message: 'No token provided' });
}
try {
const decoded = jwt.verify(header.split(' ')[1], process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select('-password');
if (!req.user) return res.status(401).json({ message: 'User not found' });
next();
} catch {
res.status(401).json({ message: 'Invalid token' });
}
};
▶ 示例:获取当前用户路由
JAVASCRIPT
const auth = require('../middleware/auth');
router.get('/me', auth, async (req, res) => {
res.json({ user: { id: req.user._id, username: req.user.username, email: req.user.email, role: req.user.role } });
});
5. app.js 主文件与 .env 配置
▶ 示例:app.js 整合
JAVASCRIPT
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const authRoutes = require('./routes/auth');
const app = express();
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.statusCode || 500).json({ message: err.message || 'Server Error' });
});
module.exports = app;
▶ 示例:.env 环境变量
TEXT
📖 仅展示
PORT=3000
MONGO_URI=mongodb://localhost:27017/task-manager
JWT_SECRET=your_super_secret_key_change_in_production
6. 综合示例:项目初始化全流程
以下代码将上面所有核心模块串联,约 80 行核心代码即可运行一个带认证的任务管理 API 骨架:
JAVASCRIPT
// server.js
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const cors = require('cors');
const helmet = require('helmet');
const app = express();
app.use(helmet(), cors(), express.json());
// --- Models ---
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });
userSchema.pre('save', async function () { if (this.isModified('password')) this.password = await bcrypt.hash(this.password, 10); });
userSchema.methods.comparePassword = function (pw) { return bcrypt.compare(pw, this.password); };
const User = mongoose.model('User', userSchema);
// --- Auth Middleware ---
const auth = async (req, res, next) => {
try {
const decoded = jwt.verify(req.headers.authorization?.split(' ')[1], process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select('-password');
next();
} catch { res.status(401).json({ message: 'Unauthorized' }); }
};
// --- Routes ---
app.post('/api/auth/register', async (req, res) => {
const user = await User.create(req.body);
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.status(201).json({ token, user: { id: user._id, username: user.username, role: user.role } });
});
app.post('/api/auth/login', async (req, res) => {
const user = await User.findOne({ email: req.body.email });
if (!user || !(await user.comparePassword(req.body.password))) return res.status(401).json({ message: 'Invalid credentials' });
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.json({ token, user: { id: user._id, username: user.username, role: user.role } });
});
app.get('/api/auth/me', auth, (req, res) => res.json(req.user));
// --- Start ---
mongoose.connect(process.env.MONGO_URI).then(() => {
app.listen(process.env.PORT || 3000, () => console.log('Server running'));
});
❓ 常见问题
Q 项目目录结构怎么设计?
A 按功能分层:models/ 存数据模型,routes/ 存路由,middleware/ 存中间件,config/ 存配置,根目录放 app.js 入口。
Q 为什么用 Mongoose 不用原生 Driver?
A Mongoose 提供 Schema 验证、中间件钩子、类型提示和查询构建器,开发效率高;原生 Driver 更轻量,适合简单场景。
Q .env 文件要提交到 Git 吗?
A 绝对不要。.env 包含数据库密码和密钥等敏感信息,应加入 .gitignore,部署时通过环境变量或 CI 注入。
Q JWT secret 要多长?
A 至少 32 字符随机字符串,生产环境建议 64 字符以上。可用 node -e "console.log(require('crypto').randomBytes(64).toString('hex'))" 生成。
Q 如何验证注册接口?
A 用 Postman 或 curl 发送 POST /api/auth/register,检查返回 token,然后用 token 访问受保护路由验证认证是否生效。
- Q: 为什么要先做认证? A: 认证是业务逻辑的前提,没有身份标识就无法实现数据归属和权限控制,后续的任务 CRUD 都依赖认证。
- Q: 密码存储安全吗? A: bcrypt 自适应哈希+盐值,远优于明文或 MD5/SHA,10 轮 cost 约 100ms/次,兼顾安全与性能。
- Q: JWT secret 怎么管理? A: 开发环境用 .env 文件,生产环境用环境变量或密钥管理服务(如 AWS Secrets Manager),绝不硬编码。
- Q: 如何测试注册/登录? A: 用 Postman 或 curl 发送 POST 请求到
/api/auth/register和/api/auth/login,检查返回的 token。 - Q: 项目结构怎么组织? A: 按 MVC 变体拆分:models(数据)、routes(路由)、middleware(中间件)、config(配置),保持关注点分离。
- Q: bcrypt 和 crypto 哪个更好? A: bcrypt 专为密码设计,内置盐值和自适应 cost,比 crypto 的 SHA 系列更适合密码哈希。
📖 小节
- 项目启动:Alice 的第一天的核心概念与使用方法
- 项目初始化与目录结构的核心概念与使用方法
- Mongoose 模型设计的核心概念与使用方法
- 用户认证模块的核心概念与使用方法
- app.js 主文件与 .env 配置的核心概念与使用方法
- 综合示例:项目初始化全流程的核心概念与使用方法
📝 作业
- 按照目录结构初始化项目,安装所有依赖,确保
npm run dev能启动服务器。 - 创建 User 和 Task 两个 Mongoose 模型,并用 Postman 测试注册和登录接口。
- 编写 auth 中间件,测试
/api/auth/me端点:不传 token 时返回 401,传正确 token 时返回用户信息。 - 在
.env中配置 JWT_SECRET 和 MONGO_URI,并在.gitignore中忽略.env文件。