从 0 到跑通测试:用 AI 搭一个 Node.js 任务管理 API(完整命令版)
AI编程 实战案例分享|附完整命令
近两年,AI 编程工具从“代码补全助手”快速进化为“可以参与需求分析、架构设计、代码生成、测试修复和文档编写的工程协作者”。很多开发者已经不再把 AI 只当作一个问答工具,而是把它嵌入到真实研发流程中:让 AI 生成脚手架、拆分任务、编写接口、补测试、解释报错、优化性能,甚至辅助完成一次完整的产品迭代。
本文不讲空泛概念,而是通过一个完整的实战案例,演示如何用 AI 辅助完成一个可运行的小项目:AI 编程任务管理 API 服务。这个项目包含用户任务的增删改查、状态管理、接口测试以及项目说明文档,适合用来理解 AI 编程在真实开发中的落地方式。
文章会包含完整命令,读者可以按步骤复现。
一、案例目标:用 AI 辅助开发一个任务管理 API
我们要完成一个后端项目,功能如下:
- 使用 Node.js + Express 搭建 API 服务;
- 使用 SQLite 作为本地数据库;
- 支持任务创建、查询、更新、删除;
- 支持任务状态:
todo、doing、done; - 提供统一错误处理;
- 编写接口测试;
- 生成 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 可以帮我们写得更快,但最终决定软件质量的,仍然是开发者的判断力、系统思维和工程经验。