Skip to content

第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测试以下接口:

  1. 获取所有Todo项

    • 方法:GET
    • URL:http://localhost:3000/api/todos
  2. 创建新Todo项

    • 方法:POST
    • URL:http://localhost:3000/api/todos
    • 请求体:
      json
      {
        "title": "Learn Node.js"
      }
  3. 获取单个Todo项

    • 方法:GET
    • URL:http://localhost:3000/api/todos/{id}
  4. 更新Todo项

    • 方法:PUT
    • URL:http://localhost:3000/api/todos/{id}
    • 请求体:
      json
      {
        "title": "Learn Node.js Advanced",
        "completed": true
      }
  5. 删除Todo项

    • 方法:DELETE
    • URL:http://localhost:3000/api/todos/{id}

12.4 代码优化:模块化拆分、错误处理

将代码模块化拆分,提高可维护性:

  1. 创建 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
};
  1. 创建 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;
  1. 更新 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入门(安装、创建数据库/表)

  1. 安装MySQL

    • Windows:下载并安装 MySQL Installer
    • Mac:使用 Homebrew 安装 brew install mysql
    • Linux:使用包管理器安装,如 apt install mysql-server
  2. 创建数据库和表

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 mysql2

12.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数据库关联,实现了数据的持久化存储。

© 2026 编程马·菜鸟教程 版权所有