Node.js: 任务 API 项目(上)

最后更新:2026-08-26

1. 项目启动:Alice 的第一天

Alice 和团队接到一个新需求——构建团队任务管理 API。第一天,他们决定先搭建项目骨架和用户认证模块。"认证是一切的基础,"Alice 说,"没有身份验证,后面的任务管理就没有意义。"



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 认证原理

100%
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 访问受保护路由验证认证是否生效。

📖 小节

📝 作业

  1. 按照目录结构初始化项目,安装所有依赖,确保 npm run dev 能启动服务器。
  2. 创建 User 和 Task 两个 Mongoose 模型,并用 Postman 测试注册和登录接口。
  3. 编写 auth 中间件,测试 /api/auth/me 端点:不传 token 时返回 401,传正确 token 时返回用户信息。
  4. .env 中配置 JWT_SECRET 和 MONGO_URI,并在 .gitignore 中忽略 .env 文件。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏