# TodoList API

URL: https://caijiao.org/express/chapter11/2-todolist
Source: docs/express/chapter11/2-todolist.md
Description: 本章节将介绍如何使用 Express 构建一个简单的 TodoList API。我们将涵盖从设计数据库、创建路由和控制器，到实现增删改查（CRUD）操作。通过这个项目，您将了解如何使用 Express 快速构建一个完整的 API 系统。

在本章节中，我们将使用 Express 构建一个简单的 TodoList API。TodoList 是一个常见的任务管理应用，用户可以添加、查看、更新和删除任务。通过实现这个 API，您将熟悉 Express 中处理请求、操作数据库以及实现 API 路由的常见方法。

## 1. 项目概述

TodoList API 将包含以下功能：

1. **添加任务**：用户可以创建新的任务。
2. **查看任务**：用户可以查看所有任务或单个任务。
3. **更新任务**：用户可以编辑任务的内容或状态。
4. **删除任务**：用户可以删除任务。
5. **标记任务完成**：用户可以将任务标记为完成。

## 2. 数据库设计

我们将使用 MongoDB 来存储数据，并通过 Mongoose 来操作数据库。TodoList API 的主要数据模型如下：

### 2.1 任务模型

任务模型包含以下字段：

- `title`: 任务标题，必填。
- `description`: 任务描述。
- `completed`: 任务是否完成，默认值为 `false`。
- `createdAt`: 创建时间。

```js
const mongoose = require('mongoose');

const taskSchema = new mongoose.Schema({
  title: { type: String, required: true },
  description: { type: String },
  completed: { type: Boolean, default: false },
  createdAt: { type: Date, default: Date.now },
});

const Task = mongoose.model('Task', taskSchema);

module.exports = Task;
````

## 3. API 路由设计

我们将设计一些基本的 API 路由，处理任务的增删改查（CRUD）操作。

### 3.1 创建任务

用户可以通过 `POST` 请求创建任务。任务的标题是必填字段，描述字段是可选的。

```js
const express = require('express');
const Task = require('../models/Task');

const router = express.Router();

router.post('/tasks', async (req, res) => {
  const { title, description } = req.body;

  try {
    const task = new Task({ title, description });
    await task.save();
    res.status(201).json({ message: 'Task created successfully', task });
  } catch (err) {
    res.status(400).json({ message: 'Error creating task', error: err });
  }
});

module.exports = router;
```

### 3.2 获取所有任务

用户可以通过 `GET` 请求获取所有任务，返回任务列表。

```js
router.get('/tasks', async (req, res) => {
  try {
    const tasks = await Task.find();
    res.json(tasks);
  } catch (err) {
    res.status(500).json({ message: 'Error fetching tasks', error: err });
  }
});
```

### 3.3 获取单个任务

用户可以通过 `GET` 请求获取单个任务，使用任务的 ID。

```js
router.get('/tasks/:id', async (req, res) => {
  const { id } = req.params;
  
  try {
    const task = await Task.findById(id);
    if (!task) {
      return res.status(404).json({ message: 'Task not found' });
    }
    res.json(task);
  } catch (err) {
    res.status(500).json({ message: 'Error fetching task', error: err });
  }
});
```

### 3.4 更新任务

用户可以通过 `PUT` 请求更新任务的标题、描述或完成状态。

```js
router.put('/tasks/:id', async (req, res) => {
  const { id } = req.params;
  const { title, description, completed } = req.body;

  try {
    const task = await Task.findById(id);
    if (!task) {
      return res.status(404).json({ message: 'Task not found' });
    }

    task.title = title || task.title;
    task.description = description || task.description;
    task.completed = completed !== undefined ? completed : task.completed;

    await task.save();
    res.json({ message: 'Task updated successfully', task });
  } catch (err) {
    res.status(500).json({ message: 'Error updating task', error: err });
  }
});
```

### 3.5 删除任务

用户可以通过 `DELETE` 请求删除任务。

```js
router.delete('/tasks/:id', async (req, res) => {
  const { id } = req.params;

  try {
    const task = await Task.findByIdAndDelete(id);
    if (!task) {
      return res.status(404).json({ message: 'Task not found' });
    }
    res.json({ message: 'Task deleted successfully' });
  } catch (err) {
    res.status(500).json({ message: 'Error deleting task', error: err });
  }
});
```

### 3.6 标记任务为完成

用户可以通过 `PATCH` 请求将任务标记为已完成或未完成。

```js
router.patch('/tasks/:id/complete', async (req, res) => {
  const { id } = req.params;

  try {
    const task = await Task.findById(id);
    if (!task) {
      return res.status(404).json({ message: 'Task not found' });
    }

    task.completed = true;
    await task.save();
    res.json({ message: 'Task marked as completed', task });
  } catch (err) {
    res.status(500).json({ message: 'Error completing task', error: err });
  }
});

router.patch('/tasks/:id/incomplete', async (req, res) => {
  const { id } = req.params;

  try {
    const task = await Task.findById(id);
    if (!task) {
      return res.status(404).json({ message: 'Task not found' });
    }

    task.completed = false;
    await task.save();
    res.json({ message: 'Task marked as incomplete', task });
  } catch (err) {
    res.status(500).json({ message: 'Error marking task as incomplete', error: err });
  }
});
```

## 4. 总结

通过本章节的实现，您已经掌握了如何使用 Express 创建一个简单的 TodoList API。您学会了如何设计数据库模型并实现常见的 CRUD 操作：创建任务、获取任务、更新任务、删除任务以及标记任务完成。此外，您还学会了如何使用 Mongoose 操作 MongoDB 数据库。

该 API 仅是一个简单的 TodoList 系统，您可以根据需求扩展更多功能，比如任务的优先级、截止日期、标签管理等。
