Appearance
第12章:进阶实战(综合应用,贴合企业开发)
实战4:TodoList 后端接口(完整接口开发)
12.1 需求分析:实现TodoList的增、删、改、查接口
我们需要实现一个完整的TodoList后端接口,支持以下功能:
- 获取所有Todo项(GET)
- 获取单个Todo项(GET)
- 创建新Todo项(POST)
- 更新Todo项(PUT)
- 删除Todo项(DELETE)
12.2 核心实现:Express路由、中间件、数据存储(本地JSON文件)
首先,创建项目并安装依赖:
bash
# 创建项目目录
mkdir todo-list-api
cd todo-list-api
# 初始化项目
npm init -y
# 安装Express
npm install express
# 安装body-parser中间件(用于解析POST请求体)
npm install body-parser创建主服务器文件 server.js:
javascript
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const path = require('path');
const app = express();
const port = 3000;
// 中间件配置
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 数据存储路径
const dataPath = path.join(__dirname, 'todos.json');
// 确保数据文件存在
if (!fs.existsSync(dataPath)) {
fs.writeFileSync(dataPath, JSON.stringify([]));
}
// 读取数据
const readTodos = () => {
const data = fs.readFileSync(dataPath, 'utf8');
return JSON.parse(data);
};
// 写入数据
const writeTodos = (todos) => {
fs.writeFileSync(dataPath, JSON.stringify(todos, null, 2));
};
// 生成唯一ID
const generateId = () => {
return Date.now().toString();
};
// 路由:获取所有Todo项
app.get('/api/todos', (req, res) => {
const todos = readTodos();
res.json(todos);
});
// 路由:获取单个Todo项
app.get('/api/todos/:id', (req, res) => {
const todos = readTodos();
const todo = todos.find(t => t.id === req.params.id);
if (!todo) {
res.status(404).json({ error: 'Todo not found' });
return;
}
res.json(todo);
});
// 路由:创建新Todo项
app.post('/api/todos', (req, res) => {
const todos = readTodos();
const newTodo = {
id: generateId(),
title: req.body.title,
completed: false,
createdAt: new Date().toISOString()
};
todos.push(newTodo);
writeTodos(todos);
res.status(201).json(newTodo);
});
// 路由:更新Todo项
app.put('/api/todos/:id', (req, res) => {
const todos = readTodos();
const index = todos.findIndex(t => t.id === req.params.id);
if (index === -1) {
res.status(404).json({ error: 'Todo not found' });
return;
}
const updatedTodo = {
...todos[index],
...req.body,
updatedAt: new Date().toISOString()
};
todos[index] = updatedTodo;
writeTodos(todos);
res.json(updatedTodo);
});
// 路由:删除Todo项
app.delete('/api/todos/:id', (req, res) => {
const todos = readTodos();
const filteredTodos = todos.filter(t => t.id !== req.params.id);
if (filteredTodos.length === todos.length) {
res.status(404).json({ error: 'Todo not found' });
return;
}
writeTodos(filteredTodos);
res.json({ message: 'Todo deleted successfully' });
});
// 启动服务器
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});12.3 接口测试:Postman测试所有接口,处理异常情况
使用Postman测试以下接口:
获取所有Todo项:
- 方法:GET
- URL:
http://localhost:3000/api/todos
创建新Todo项:
- 方法:POST
- URL:
http://localhost:3000/api/todos - 请求体:json
{ "title": "Learn Node.js" }
获取单个Todo项:
- 方法:GET
- URL:
http://localhost:3000/api/todos/{id}
更新Todo项:
- 方法:PUT
- URL:
http://localhost:3000/api/todos/{id} - 请求体:json
{ "title": "Learn Node.js Advanced", "completed": true }
删除Todo项:
- 方法:DELETE
- URL:
http://localhost:3000/api/todos/{id}
12.4 代码优化:模块化拆分、错误处理
将代码模块化拆分,提高可维护性:
- 创建
utils.js工具文件:
javascript
const fs = require('fs');
const path = require('path');
// 数据存储路径
const dataPath = path.join(__dirname, 'todos.json');
// 确保数据文件存在
const ensureDataFile = () => {
if (!fs.existsSync(dataPath)) {
fs.writeFileSync(dataPath, JSON.stringify([]));
}
};
// 读取数据
const readTodos = () => {
ensureDataFile();
const data = fs.readFileSync(dataPath, 'utf8');
return JSON.parse(data);
};
// 写入数据
const writeTodos = (todos) => {
fs.writeFileSync(dataPath, JSON.stringify(todos, null, 2));
};
// 生成唯一ID
const generateId = () => {
return Date.now().toString();
};
module.exports = {
readTodos,
writeTodos,
generateId
};- 创建
routes.js路由文件:
javascript
const express = require('express');
const { readTodos, writeTodos, generateId } = require('./utils');
const router = express.Router();
// 路由:获取所有Todo项
router.get('/', (req, res) => {
try {
const todos = readTodos();
res.json(todos);
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
// 路由:获取单个Todo项
router.get('/:id', (req, res) => {
try {
const todos = readTodos();
const todo = todos.find(t => t.id === req.params.id);
if (!todo) {
res.status(404).json({ error: 'Todo not found' });
return;
}
res.json(todo);
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
// 路由:创建新Todo项
router.post('/', (req, res) => {
try {
const todos = readTodos();
const newTodo = {
id: generateId(),
title: req.body.title,
completed: false,
createdAt: new Date().toISOString()
};
todos.push(newTodo);
writeTodos(todos);
res.status(201).json(newTodo);
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
// 路由:更新Todo项
router.put('/:id', (req, res) => {
try {
const todos = readTodos();
const index = todos.findIndex(t => t.id === req.params.id);
if (index === -1) {
res.status(404).json({ error: 'Todo not found' });
return;
}
const updatedTodo = {
...todos[index],
...req.body,
updatedAt: new Date().toISOString()
};
todos[index] = updatedTodo;
writeTodos(todos);
res.json(updatedTodo);
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
// 路由:删除Todo项
router.delete('/:id', (req, res) => {
try {
const todos = readTodos();
const filteredTodos = todos.filter(t => t.id !== req.params.id);
if (filteredTodos.length === todos.length) {
res.status(404).json({ error: 'Todo not found' });
return;
}
writeTodos(filteredTodos);
res.json({ message: 'Todo deleted successfully' });
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;- 更新
server.js文件:
javascript
const express = require('express');
const bodyParser = require('body-parser');
const todoRoutes = require('./routes');
const app = express();
const port = 3000;
// 中间件配置
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 路由配置
app.use('/api/todos', todoRoutes);
// 全局错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal server error' });
});
// 启动服务器
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});实战5:Node.js 连接数据库(基础)
12.5 数据库基础:MySQL入门(安装、创建数据库/表)
安装MySQL:
- Windows:下载并安装 MySQL Installer
- Mac:使用 Homebrew 安装
brew install mysql - Linux:使用包管理器安装,如
apt install mysql-server
创建数据库和表:
sql
-- 创建数据库
CREATE DATABASE todo_list;
-- 使用数据库
USE todo_list;
-- 创建todo表
CREATE TABLE todos (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);12.6 第三方包:mysql2 安装与使用
安装 mysql2 包:
bash
npm install mysql212.7 核心实现:连接数据库、执行SQL语句(查询、新增、修改、删除)
创建 db.js 数据库配置文件:
javascript
const mysql = require('mysql2');
// 创建数据库连接池
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'your_password',
database: 'todo_list',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// 导出连接池
module.exports = pool.promise();12.8 实操:将TodoList接口与MySQL关联,实现数据持久化
更新 routes.js 文件,使用MySQL数据库:
javascript
const express = require('express');
const db = require('./db');
const router = express.Router();
// 路由:获取所有Todo项
router.get('/', async (req, res) => {
try {
const [rows] = await db.query('SELECT * FROM todos');
res.json(rows);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
// 路由:获取单个Todo项
router.get('/:id', async (req, res) => {
try {
const [rows] = await db.query('SELECT * FROM todos WHERE id = ?', [req.params.id]);
if (rows.length === 0) {
res.status(404).json({ error: 'Todo not found' });
return;
}
res.json(rows[0]);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
// 路由:创建新Todo项
router.post('/', async (req, res) => {
try {
const { title, completed = false } = req.body;
const [result] = await db.query(
'INSERT INTO todos (title, completed) VALUES (?, ?)',
[title, completed]
);
const [newTodo] = await db.query('SELECT * FROM todos WHERE id = ?', [result.insertId]);
res.status(201).json(newTodo[0]);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
// 路由:更新Todo项
router.put('/:id', async (req, res) => {
try {
const { title, completed } = req.body;
const [result] = await db.query(
'UPDATE todos SET title = ?, completed = ? WHERE id = ?',
[title, completed, req.params.id]
);
if (result.affectedRows === 0) {
res.status(404).json({ error: 'Todo not found' });
return;
}
const [updatedTodo] = await db.query('SELECT * FROM todos WHERE id = ?', [req.params.id]);
res.json(updatedTodo[0]);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
// 路由:删除Todo项
router.delete('/:id', async (req, res) => {
try {
const [result] = await db.query('DELETE FROM todos WHERE id = ?', [req.params.id]);
if (result.affectedRows === 0) {
res.status(404).json({ error: 'Todo not found' });
return;
}
res.json({ message: 'Todo deleted successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;现在,我们的TodoList接口已经与MySQL数据库关联,实现了数据的持久化存储。
