检查点:完整的全栈应用
前端 + JWT 后端 + 数据库的结业项目。
2 周
Checkpoint: 完整应用
你将完成什么
把前端 Todo 应用和后端 API 整合,实现完整的全栈应用:用户注册登录、Todo CRUD、数据持久化。
验收标准
- 前端 React + 后端 Express + 数据库 PostgreSQL
- JWT 认证保护 API
- 前端路由(React Router)
- 部署到服务器
架构
前端(React)
├── 登录页 /login
├── 注册页 /register
└── Todo 页 /todos(需登录)
后端(Express)
├── POST /api/register
├── POST /api/login
├── GET /api/todos(需认证)
├── POST /api/todos(需认证)
├── PUT /api/todos/:id(需认证)
└── DELETE /api/todos/:id(需认证)
数据库(PostgreSQL)
├── users 表
└── todos 表
核心代码
后端:认证中间件
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.substring(7);
if (!token) {
return res.status(401).json({ code: 401, msg: '未登录' });
}
try {
const payload = jwt.verify(token, SECRET);
req.userId = payload.userId;
next();
} catch (err) {
res.status(401).json({ code: 401, msg: 'Token 无效' });
}
}
module.exports = authMiddleware;
后端:Todo 路由
const auth = require('./authMiddleware');
// 获取当前用户的 todos
app.get('/api/todos', auth, async (req, res) => {
const result = await pool.query(
'SELECT * FROM todo WHERE user_id = $1 ORDER BY created_at DESC',
[req.userId]
);
res.json({ code: 0, data: result.rows });
});
// 创建 todo
app.post('/api/todos', auth, async (req, res) => {
const { text } = req.body;
if (!text) {
return res.status(400).json({ code: 400, msg: '内容不能为空' });
}
const result = await pool.query(
'INSERT INTO todo (text, user_id) VALUES ($1, $2) RETURNING *',
[text, req.userId]
);
res.status(201).json({ code: 0, data: result.rows[0] });
});
// 更新 todo
app.put('/api/todos/:id', auth, async (req, res) => {
const { id } = req.params;
const { text, completed } = req.body;
const result = await pool.query(
'UPDATE todo SET text = $1, completed = $2 WHERE id = $3 AND user_id = $4 RETURNING *',
[text, completed, id, req.userId]
);
if (result.rows.length === 0) {
return res.status(404).json({ code: 404, msg: 'Todo 不存在' });
}
res.json({ code: 0, data: result.rows[0] });
});
// 删除 todo
app.delete('/api/todos/:id', auth, async (req, res) => {
const { id } = req.params;
const result = await pool.query(
'DELETE FROM todo WHERE id = $1 AND user_id = $2 RETURNING id',
[id, req.userId]
);
if (result.rows.length === 0) {
return res.status(404).json({ code: 404, msg: 'Todo 不存在' });
}
res.status(204).send();
});