上一篇 下一篇 分享链接 返回 返回顶部

从 0 到跑通测试:用 AI 搭一个 Node.js 任务管理 API(完整命令版)

发布人:慈云数据-客服中心 发布时间:24小时前 阅读量:3

AI编程 实战案例分享|附完整命令

近两年,AI 编程工具从“代码补全助手”快速进化为“可以参与需求分析、架构设计、代码生成、测试修复和文档编写的工程协作者”。很多开发者已经不再把 AI 只当作一个问答工具,而是把它嵌入到真实研发流程中:让 AI 生成脚手架、拆分任务、编写接口、补测试、解释报错、优化性能,甚至辅助完成一次完整的产品迭代。

本文不讲空泛概念,而是通过一个完整的实战案例,演示如何用 AI 辅助完成一个可运行的小项目:AI 编程任务管理 API 服务。这个项目包含用户任务的增删改查、状态管理、接口测试以及项目说明文档,适合用来理解 AI 编程在真实开发中的落地方式。

文章会包含完整命令,读者可以按步骤复现。


一、案例目标:用 AI 辅助开发一个任务管理 API

我们要完成一个后端项目,功能如下:

  • 使用 Node.js + Express 搭建 API 服务;
  • 使用 SQLite 作为本地数据库;
  • 支持任务创建、查询、更新、删除;
  • 支持任务状态:tododoingdone
  • 提供统一错误处理;
  • 编写接口测试;
  • 生成 README 文档;
  • 使用 AI 辅助生成代码、修复问题和补充测试。

最终项目接口包括:

方法 路径 功能
GET /health 健康检查
GET /tasks 获取任务列表
GET /tasks/:id 获取单个任务
POST /tasks 创建任务
PATCH /tasks/:id 更新任务
DELETE /tasks/:id 删除任务

二、开发环境准备

本文使用 Node.js 作为示例环境。如果你已经安装了 Node.js,可以跳过安装步骤。

查看版本:

node -v
npm -v

建议版本:

node >= 18
npm >= 9

创建项目目录:

mkdir ai-coding-task-api
cd ai-coding-task-api

初始化项目:

npm init -y

安装依赖:

npm install express better-sqlite3 cors morgan dotenv

安装开发依赖:

npm install -D nodemon jest supertest

修改 package.json,添加脚本:

npm pkg set scripts.start="node src/server.js"
npm pkg set scripts.dev="nodemon src/server.js"
npm pkg set scripts.test="jest"

创建目录结构:

mkdir -p src db tests
touch src/server.js src/app.js src/database.js src/taskRepository.js src/taskRoutes.js
touch tests/task.test.js
touch .env README.md

此时项目结构大致如下:

ai-coding-task-api
├── db
├── src
│   ├── app.js
│   ├── database.js
│   ├── server.js
│   ├── taskRepository.js
│   └── taskRoutes.js
├── tests
│   └── task.test.js
├── .env
├── package.json
└── README.md

三、如何让 AI 参与开发?

很多人使用 AI 编程时,只会输入一句:“帮我写一个接口”。这样往往能得到代码,但代码质量不稳定,也不一定适合项目结构。

更推荐的方式是把 AI 当作一个初级工程师或结对编程伙伴,给它明确的上下文、约束和输出要求。

例如可以这样向 AI 提问:

你是一个资深 Node.js 后端工程师。请基于 Express + better-sqlite3 帮我实现一个任务管理 API。

要求:
1. 使用分层结构:app.js、server.js、database.js、taskRepository.js、taskRoutes.js;
2. 数据库使用 SQLite;
3. tasks 表包含 id、title、description、status、created_at、updated_at;
4. status 只能是 todo、doing、done;
5. 接口需要有参数校验和统一错误返回;
6. 请给出每个文件的完整代码;
7. 代码要适合直接运行。

这样的 Prompt 明确了角色、技术栈、文件结构、数据模型、校验规则和输出格式,AI 生成的结果通常更接近可用代码。

下面我们直接给出完整实现。


四、核心代码实现

1. 配置环境变量

编辑 .env

cat > .env <<'EOF'
PORT=3000
DB_PATH=./db/tasks.sqlite
EOF

2. 数据库连接与初始化

编辑 src/database.js

cat > src/database.js <<'EOF'
const Database = require('better-sqlite3');
const path = require('path');
const fs = require('fs');
require('dotenv').config();

const dbPath = process.env.DB_PATH || './db/tasks.sqlite';
const dir = path.dirname(dbPath);

if (!fs.existsSync(dir)) {
  fs.mkdirSync(dir, { recursive: true });
}

const db = new Database(dbPath);

db.pragma('journal_mode = WAL');

db.exec(`
  CREATE TABLE IF NOT EXISTS tasks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    description TEXT DEFAULT '',
    status TEXT NOT NULL DEFAULT 'todo',
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
  );
`);

module.exports = db;
EOF

这里使用 better-sqlite3,它是一个同步 SQLite 库,适合轻量 API 服务或本地工具类项目。


3. 编写任务数据访问层

编辑 src/taskRepository.js

cat > src/taskRepository.js <<'EOF'
const db = require('./database');

const VALID_STATUS = ['todo', 'doing', 'done'];

function now() {
  return new Date().toISOString();
}

function validateStatus(status) {
  return VALID_STATUS.includes(status);
}

function findAll() {
  return db.prepare(`
    SELECT id, title, description, status, created_at, updated_at
    FROM tasks
    ORDER BY id DESC
  `).all();
}

function findById(id) {
  return db.prepare(`
    SELECT id, title, description, status, created_at, updated_at
    FROM tasks
    WHERE id = ?
  `).get(id);
}

function createTask({ title, description = '', status = 'todo' }) {
  if (!title || typeof title !== 'string' || !title.trim()) {
    const error = new Error('title is required');
    error.statusCode = 400;
    throw error;
  }

  if (!validateStatus(status)) {
    const error = new Error('invalid status');
    error.statusCode = 400;
    throw error;
  }

  const createdAt = now();
  const updatedAt = createdAt;

  const result = db.prepare(`
    INSERT INTO tasks (title, description, status, created_at, updated_at)
    VALUES (?, ?, ?, ?, ?)
  `).run(title.trim(), description || '', status, createdAt, updatedAt);

  return findById(result.lastInsertRowid);
}

function updateTask(id, payload) {
  const existing = findById(id);

  if (!existing) {
    const error = new Error('task not found');
    error.statusCode = 404;
    throw error;
  }

  const title = payload.title !== undefined ? payload.title : existing.title;
  const description = payload.description !== undefined ? payload.description : existing.description;
  const status = payload.status !== undefined ? payload.status : existing.status;

  if (!title || typeof title !== 'string' || !title.trim()) {
    const error = new Error('title cannot be empty');
    error.statusCode = 400;
    throw error;
  }

  if (!validateStatus(status)) {
    const error = new Error('invalid status');
    error.statusCode = 400;
    throw error;
  }

  db.prepare(`
    UPDATE tasks
    SET title = ?, description = ?, status = ?, updated_at = ?
    WHERE id = ?
  `).run(title.trim(), description || '', status, now(), id);

  return findById(id);
}

function deleteTask(id) {
  const existing = findById(id);

  if (!existing) {
    const error = new Error('task not found');
    error.statusCode = 404;
    throw error;
  }

  db.prepare(`DELETE FROM tasks WHERE id = ?`).run(id);

  return existing;
}

module.exports = {
  findAll,
  findById,
  createTask,
  updateTask,
  deleteTask,
  VALID_STATUS
};
EOF

这一层封装了所有数据库操作。这样做的好处是路由层不直接拼 SQL,后续如果要换成 MySQL、PostgreSQL 或 ORM,影响范围更小。


4. 编写路由层

编辑 src/taskRoutes.js

cat > src/taskRoutes.js <<'EOF'
const express = require('express');
const router = express.Router();

const taskRepository = require('./taskRepository');

function parseId(req) {
  const id = Number(req.params.id);

  if (!Number.isInteger(id) || id <= 0) {
    const error = new Error('invalid id');
    error.statusCode = 400;
    throw error;
  }

  return id;
}

router.get('/tasks', (req, res, next) => {
  try {
    const tasks = taskRepository.findAll();
    res.json({ data: tasks });
  } catch (error) {
    next(error);
  }
});

router.get('/tasks/:id', (req, res, next) => {
  try {
    const id = parseId(req);
    const task = taskRepository.findById(id);

    if (!task) {
      return res.status(404).json({ error: 'task not found' });
    }

    res.json({ data: task });
  } catch (error) {
    next(error);
  }
});

router.post('/tasks', (req, res, next) => {
  try {
    const task = taskRepository.createTask(req.body);
    res.status(201).json({ data: task });
  } catch (error) {
    next(error);
  }
});

router.patch('/tasks/:id', (req, res, next) => {
  try {
    const id = parseId(req);
    const task = taskRepository.updateTask(id, req.body);
    res.json({ data: task });
  } catch (error) {
    next(error);
  }
});

router.delete('/tasks/:id', (req, res, next) => {
  try {
    const id = parseId(req);
    const task = taskRepository.deleteTask(id);
    res.json({ data: task });
  } catch (error) {
    next(error);
  }
});

module.exports = router;
EOF

5. 编写 Express 应用入口

编辑 src/app.js

cat > src/app.js <<'EOF'
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');

const taskRoutes = require('./taskRoutes');

const app = express();

app.use(cors());
app.use(express.json());
app.use(morgan('dev'));

app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    timestamp: new Date().toISOString()
  });
});

app.use(taskRoutes);

app.use((req, res) => {
  res.status(404).json({
    error: 'route not found'
  });
});

app.use((err, req, res, next) => {
  const statusCode = err.statusCode || 500;

  res.status(statusCode).json({
    error: err.message || 'internal server error'
  });
});

module.exports = app;
EOF

6. 编写服务启动文件

编辑 src/server.js

cat > src/server.js <<'EOF'
require('dotenv').config();

const app = require('./app');

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Task API server is running at http://localhost:${PORT}`);
});
EOF

五、运行项目并测试接口

启动开发服务:

npm run dev

如果看到类似输出,说明服务启动成功:

Task API server is running at http://localhost:3000

健康检查:

curl http://localhost:3000/health

创建任务:

curl -X POST http://localhost:3000/tasks \
  -H "Content-Type: application/json" \
  -d '{
    "title": "学习 AI 编程",
    "description": "使用 AI 辅助完成一个 Express API 项目",
    "status": "todo"
  }'

查询任务列表:

curl http://localhost:3000/tasks

查询单个任务:

curl http://localhost:3000/tasks/1

更新任务状态:

curl -X PATCH http://localhost:3000/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{
    "status": "doing"
  }'

完成任务:

curl -X PATCH http://localhost:3000/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{
    "status": "done"
  }'

删除任务:

curl -X DELETE http://localhost:3000/tasks/1

测试非法状态:

curl -X POST http://localhost:3000/tasks \
  -H "Content-Type: application/json" \
  -d '{
    "title": "错误状态任务",
    "status": "invalid"
  }'

预期返回:

{
  "error": "invalid status"
}

六、让 AI 帮我们补测试

当主功能完成后,可以让 AI 根据已有代码生成测试。一个推荐 Prompt 如下:

请基于当前 Express 项目,为 tasks API 编写 Jest + Supertest 测试。

要求:
1. 测试 health 接口;
2. 测试创建任务成功;
3. 测试 title 为空时返回 400;
4. 测试 status 非法时返回 400;
5. 测试查询任务列表;
6. 测试更新任务状态;
7. 测试删除任务;
8. 请给出 tests/task.test.js 的完整代码。

下面是测试代码。

编辑 tests/task.test.js

cat > tests/task.test.js <<'EOF'
const request = require('supertest');
const app = require('../src/app');

describe('Task API', () => {
  let createdTaskId;

  test('GET /health should return ok', async () => {
    const res = await request(app).get('/health');

    expect(res.statusCode).toBe(200);
    expect(res.body.status).toBe('ok');
  });

  test('POST /tasks should create a task', async () => {
    const res = await request(app)
      .post('/tasks')
      .send({
        title: 'AI coding test task',
        description: 'created by jest',
        status: 'todo'
      });

    expect(res.statusCode).toBe(201);
    expect(res.body.data).toHaveProperty('id');
    expect(res.body.data.title).toBe('AI coding test task');

    createdTaskId = res.body.data.id;
  });

  test('POST /tasks should reject empty title', async () => {
    const res = await request(app)
      .post('/tasks')
      .send({
        title: '',
        status: 'todo'
      });

    expect(res.statusCode).toBe(400);
    expect(res.body.error).toBe('title is required');
  });

  test('POST /tasks should reject invalid status', async () => {
    const res = await request(app)
      .post('/tasks')
      .send({
        title: 'Invalid status task',
        status: 'invalid'
      });

    expect(res.statusCode).toBe(400);
    expect(res.body.error).toBe('invalid status');
  });

  test('GET /tasks should return task list', async () => {
    const res = await request(app).get('/tasks');

    expect(res.statusCode).toBe(200);
    expect(Array.isArray(res.body.data)).toBe(true);
  });

  test('GET /tasks/:id should return one task', async () => {
    const res = await request(app).get(`/tasks/${createdTaskId}`);

    expect(res.statusCode).toBe(200);
    expect(res.body.data.id).toBe(createdTaskId);
  });

  test('PATCH /tasks/:id should update task status', async () => {
    const res = await request(app)
      .patch(`/tasks/${createdTaskId}`)
      .send({
        status: 'done'
      });

    expect(res.statusCode).toBe(200);
    expect(res.body.data.status).toBe('done');
  });

  test('DELETE /tasks/:id should delete task', async () => {
    const res = await request(app).delete(`/tasks/${createdTaskId}`);

    expect(res.statusCode).toBe(200);
    expect(res.body.data.id).toBe(createdTaskId);
  });

  test('GET deleted task should return 404', async () => {
    const res = await request(app).get(`/tasks/${createdTaskId}`);

    expect(res.statusCode).toBe(404);
  });
});
EOF

运行测试:

npm test

如果测试通过,说明接口基础功能已经稳定。


七、让 AI 帮我们生成 README

项目做完后,很多开发者容易忽略文档。其实文档非常适合交给 AI 生成初稿,再由人补充项目背景和注意事项。

可以使用如下 Prompt:

请为这个 Node.js + Express + SQLite 的任务管理 API 项目生成 README。

要求包含:
1. 项目介绍;
2. 技术栈;
3. 安装步骤;
4. 启动命令;
5. API 列表;
6. curl 调用示例;
7. 测试命令;
8. 目录结构。

编辑 README.md

cat > README.md <<'EOF'
# AI Coding Task API

一个使用 Node.js、Express 和 SQLite 构建的任务管理 API 示例项目,适合用于学习 AI 辅助编程、接口开发和自动化测试。

## 技术栈

- Node.js
- Express
- SQLite
- better-sqlite3
- Jest
- Supertest

## 安装依赖

```bash
npm install

启动开发服务

npm run dev

启动生产服务

npm start

运行测试

npm test

API

健康检查

GET /health

创建任务

POST /tasks

请求示例:

{
  "title": "学习 AI 编程",
  "description": "完成一个 API 项目",
  "status": "todo"
}

获取任务列表

GET /tasks

获取单个任务

GET /tasks/:id

更新任务

PATCH /tasks/:id

删除任务

DELETE /tasks/:id

任务状态

任务状态只能是:

  • todo
  • doing
  • done EOF

八、一次性完整命令汇总

如果你想快速创建整个项目,可以按以下命令执行。

mkdir ai-coding-task-api
cd ai-coding-task-api

npm init -y

npm install express better-sqlite3 cors morgan dotenv
npm install -D nodemon jest supertest

npm pkg set scripts.start="node src/server.js"
npm pkg set scripts.dev="nodemon src/server.js"
npm pkg set scripts.test="jest"

mkdir -p src db tests

touch src/server.js src/app.js src/database.js src/taskRepository.js src/taskRoutes.js
touch tests/task.test.js
touch .env README.md

然后依次把上文中的代码写入对应文件,最后执行:

npm run dev

另开一个终端测试接口:

curl http://localhost:3000/health

运行自动化测试:

npm test

九、AI 编程实战中的关键经验

通过这个案例,我们可以总结出几个非常实用的经验。

1. 不要只让 AI 写代码,要让它理解结构

如果你只说“帮我写一个任务管理接口”,AI 往往会把所有代码写在一个文件里。这样虽然能运行,但不利于维护。更好的方式是提前规定目录结构、职责划分和编码规范。

例如:

请按照 controller、repository、route 三层结构实现,不要把 SQL 写在路由函数里。

2. Prompt 越具体,代码越稳定

高质量 Prompt 应该包含:

  • 技术栈;
  • 项目背景;
  • 数据结构;
  • 接口定义;
  • 错误处理规则;
  • 文件路径;
  • 输出格式;
  • 是否需要测试。

AI 不怕需求多,怕需求模糊。

3. 让 AI 做“增量修改”,不要频繁重写

真实项目中,不建议每次都让 AI “重新生成完整项目”。更好的做法是:

请只修改 src/taskRepository.js,增加根据 status 过滤任务的能力,不要改动其他文件。

这样能减少无关代码变动,也方便代码审查。

4. 测试非常适合交给 AI 补齐

AI 生成业务代码时可能会遗漏边界情况,但让 AI 根据已有接口补测试,效果通常很好。尤其是以下场景:

  • 参数为空;
  • ID 非法;
  • 状态值非法;
  • 资源不存在;
  • 重复提交;
  • 权限不足;
  • 数据格式错误。

5. 人仍然要负责架构和验收

AI 可以提升编码速度,但不能替代工程判断。开发者仍然要关注:

  • 数据库设计是否合理;
  • 接口是否符合业务语义;
  • 错误码是否统一;
  • 安全性是否足够;
  • 测试覆盖是否完整;
  • 生成代码是否存在隐藏问题。

AI 编程的最佳模式不是“让 AI 替我写完”,而是“我负责方向和质量,AI 负责加速实现”。


十、可以继续扩展的方向

这个项目只是一个起点。如果你想继续练习,可以让 AI 帮你扩展以下功能:

1. 增加任务搜索

Prompt 示例:

请为 GET /tasks 增加 keyword 和 status 查询参数。
要求:
1. keyword 支持模糊搜索 title 和 description;
2. status 只能是 todo、doing、done;
3. 请修改 taskRepository.js 和 taskRoutes.js;
4. 请补充对应测试。

2. 增加分页

Prompt 示例:

请为 GET /tasks 增加分页能力。
要求:
1. 支持 page 和 pageSize;
2. 默认 page=1,pageSize=10;
3. 返回 total、page、pageSize、items;
4. page 和 pageSize 需要参数校验;
5. 请给出完整修改代码。

3. 增加用户认证

Prompt 示例:

请为当前 Express 项目增加 JWT 登录认证。
要求:
1. 新增 users 表;
2. 支持注册和登录;
3. 密码使用 bcrypt 加密;
4. tasks 表增加 user_id;
5. 用户只能访问自己的任务;
6. 请给出数据库迁移方案和完整代码。

安装相关依赖:

npm install jsonwebtoken bcryptjs

4. 接入大模型生成任务建议

如果你希望项目更贴近 AI 应用,可以增加一个接口:

POST /ai/task-suggestions

输入一个目标,例如:

{
  "goal": "三天内学习 Express 并完成一个 API 项目"
}

由大模型返回任务拆解建议。这个方向可以进一步演变为 AI To-do、AI 项目管理助手或智能学习计划工具。


结语

AI 编程真正有价值的地方,不只是“自动生成几段代码”,而是把它融入完整的软件开发流程:从需求拆解、项目初始化、代码实现、测试补齐、错误修复到文档生成。本文通过一个任务管理 API 项目展示了 AI 编程的实战路径,并提供了从环境准备到接口测试的完整命令。

对于开发者来说,未来的核心竞争力不是“会不会用 AI 写代码”,而是能否清晰表达需求、设计合理架构、识别代码质量,并把 AI 变成稳定的工程生产力。AI 可以帮我们写得更快,但最终决定软件质量的,仍然是开发者的判断力、系统思维和工程经验。

目录结构
全文