
1. 为什么我劝你用 AI 搭一个 API 服务来练手这两年 AI 辅助编程的工具越来越顺手但很多人对它的用法还停留在“帮我写个函数”“解释一下这段报错”这种碎片化的层面。我自己的体会是真正能让你快速摸清一个 AI 编码助手能力边界的办法是拿一个完整的小项目从头到尾跑一遍让它参与需求拆解、代码生成、调试排错的全过程。而“用 AI 从零搭一个 API 服务”就是这么一个特别合适的练手项目——它足够小一两天就能跑通又足够完整涉及路由设计、数据处理、错误处理、接口测试这些后端开发的核心环节。这篇文章我想聊的就是这么一件事假设你手上有一个 AI 编码助手不管是网页版对话还是集成在编辑器里的插件怎么用它从零开始搭出一个能跑、能用、结构还算像样的 API 服务。技术栈我选的是Node.js Express JavaScript原因很简单生态成熟、上手快、AI 对这套组合的代码生成质量普遍很高你不需要花太多精力在环境折腾上可以把注意力放在“怎么和 AI 协作”这件事本身。适合谁来读如果你是会一点 JavaScript、想了解后端 API 是怎么回事的前端同学或者是刚入门编程、想找个完整项目练手的新手再或者是已经会写 API、但想看看 AI 辅助开发到底能提效到什么程度的老手这篇内容应该都能给你一些可以直接抄作业的东西。我会把整个流程拆成设计思路、核心实现、实操步骤、踩坑排查几个部分每个环节都告诉你我为什么这么选、AI 在哪些地方帮了大忙、哪些地方又必须自己把关。先说一个贯穿全文的观点AI 是加速器不是自动驾驶。它能帮你把重复的、模板化的代码瞬间写出来但项目的结构怎么定、接口怎么设计、边界情况怎么处理这些判断还是得你自己来。把 AI 当成一个手速极快但需要你 review 的初级搭档这个心态摆正了整个协作过程会顺畅很多。2. 动手之前把项目结构和接口设计想清楚2.1 先明确这个 API 服务到底要干什么很多人一上来就让 AI “帮我写一个 API 服务”结果生成一堆不知道干嘛用的代码。我的习惯是先花十分钟把需求写清楚哪怕只是几句话。这次我给自己定的目标是做一个待办事项管理服务Todo API功能不用多但每个环节都要完整能创建一条待办包含标题、描述、是否完成、创建时间能查询全部待办支持按完成状态过滤能查询单条待办能更新一条待办改标题、改状态能删除一条待办所有接口返回统一的 JSON 结构出错时有明确的错误码和提示为什么选待办事项因为它是最经典的 CRUD 场景增删改查全覆盖但又没有复杂的业务逻辑干扰你理解 API 服务的骨架。等你把这套跑通了换成用户管理、订单管理、文章管理套路是一模一样的。需求定完我会把这段话直接丢给 AI让它帮我确认理解、补充我可能漏掉的点。比如它可能会提醒你“要不要加一个分页参数”“删除是软删除还是硬删除”“时间戳用什么格式”。这些提醒本身就很有价值相当于帮你做了一轮需求评审。2.2 目录结构为什么这么分在让 AI 写代码之前我强烈建议先把目录结构定下来。因为 AI 生成代码时如果你不给它一个明确的文件划分它很容易把所有逻辑塞进一个app.js里几百行堆在一起后期想改都无从下手。我用的结构是这样的todo-api/ ├── src/ │ ├── app.js # Express 应用实例中间件和路由挂载 │ ├── server.js # 启动入口监听端口 │ ├── routes/ │ │ └── todos.js # 待办相关的路由定义 │ ├── controllers/ │ │ └── todoController.js # 业务逻辑处理 │ ├── models/ │ │ └── todoModel.js # 数据存取先用内存模拟 │ ├── middlewares/ │ │ ├── errorHandler.js # 统一错误处理 │ │ └── logger.js # 请求日志 │ └── utils/ │ └── response.js # 统一响应格式封装 ├── tests/ │ └── todo.test.js # 接口测试 ├── package.json └── .env这个分层不是过度设计而是有实际好处的。路由层只负责“什么 URL 对应什么处理函数”控制器层负责“拿到请求参数后怎么处理业务”模型层负责“数据怎么存怎么取”。三层分开之后你以后想把内存存储换成数据库只需要改模型层想加一个鉴权中间件只需要在 app.js 里挂一下。AI 在生成代码时你把这个结构告诉它它就会按文件分别输出而不是一锅乱炖。提示如果你只是想做一次性验证确实可以全写一个文件里。但只要这个项目你打算继续扩展哪怕只是加两三个功能分层结构省下的时间也远超你多敲的那几行。2.3 接口设计URL 和 HTTP 方法怎么定接口设计这块我让 AI 帮我列了一版然后自己再调整。核心原则是URL 用名词表示资源HTTP 方法表示动作。最终定下来是这样功能方法路径说明查询全部待办GET/api/todos支持?completedtrue过滤查询单条待办GET/api/todos/:idid 不存在返回 404创建待办POST/api/todos请求体带 title、description更新待办PUT/api/todos/:id全量更新部分更新PATCH/api/todos/:id只改传入的字段删除待办DELETE/api/todos/:id成功返回 204这里有个细节值得说为什么同时保留 PUT 和 PATCHPUT 是“用新数据整体替换”PATCH 是“只改我传的字段”。实际开发里 PATCH 更常用因为前端往往只想改一个状态字段不想把整条数据都传回来。让 AI 两个都生成你对比一下实现差异对理解 RESTful 设计很有帮助。统一响应格式我也提前定了避免每个接口返回结构五花八门// 成功 { code: 0, message: success, data: { ... } } // 失败 { code: 40001, message: 标题不能为空, data: null }这个格式定下来之后前端对接会非常省心因为不管调哪个接口判断成功失败都只看code字段。AI 生成代码时你把这个约定写进提示词它就会在每个控制器里都按这个格式返回。3. 核心实现让 AI 写代码但你要知道每行在干嘛3.1 环境准备Node.js 装哪个版本动手第一步是装 Node.js。这里有个小坑网上教程有的让你装最新版有的让你装 LTS 版。我的建议是永远选 LTS长期支持版。LTS 版本经过充分测试生态兼容性最好不会出现某个依赖包不支持你版本的情况。截至我写这篇内容时Node.js 20.x 是稳定的 LTS 选择。安装方式看你的系统。Windows 和 macOS 直接去官网下载安装包一路下一步就行。Linux 用户比如 Ubuntu我习惯用 NodeSource 的源来装比系统自带的版本新# 添加 NodeSource 源以 Node.js 20 为例 curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs # 验证 node -v # 应输出 v20.x.x npm -v # 应输出对应版本装完之后在项目目录里初始化mkdir todo-api cd todo-api npm init -y npm install express npm install --save-dev nodemonnodemon是开发时的好帮手它会在你改代码后自动重启服务省得每次手动 CtrlC 再重跑。在package.json里加一行脚本scripts: { dev: nodemon src/server.js, start: node src/server.js }注意如果你在安装 Node.js 时遇到类似“该版本尚未发布或不可用”的报错八成是版本号写错了或者源里没有这个版本。换成官方明确标注的 LTS 版本号即可别硬试那些还没正式发布的版本。3.2 入口文件app.js 和 server.js 为什么要分开这是很多人会忽略的一个细节。把 Express 应用实例app和启动监听server分成两个文件最大的好处是方便测试。测试框架在跑接口测试时只需要引入 app 这个实例不需要真的去占用一个端口。如果全写在一起测试时就会遇到端口冲突、进程不退出的问题。app.js里主要做三件事挂载中间件、挂载路由、挂载错误处理。我让 AI 生成的版本大致是这样const express require(express); const todoRoutes require(./routes/todos); const logger require(./middlewares/logger); const errorHandler require(./middlewares/errorHandler); const app express(); // 解析 JSON 请求体 app.use(express.json()); // 请求日志 app.use(logger); // 路由 app.use(/api/todos, todoRoutes); // 统一错误处理必须放最后 app.use(errorHandler); module.exports app;server.js就简单了只负责启动const app require(./app); const PORT process.env.PORT || 3000; app.listen(PORT, () { console.log(服务已启动监听端口 ${PORT}); });这里有个顺序问题必须强调错误处理中间件一定要放在所有路由之后。Express 的中间件是按注册顺序执行的错误处理中间件有四个参数(err, req, res, next)只有放在最后才能捕获到前面路由抛出的错误。我见过不少人把它写在路由前面结果错误根本捕获不到排查半天。3.3 数据模型先用内存别急着上数据库新手最容易犯的错是一上来就纠结用 MySQL 还是 MongoDB结果环境还没配好就放弃了。我的建议是第一版用内存数组模拟数据把 API 的逻辑跑通再说。模型层这样写// models/todoModel.js let todos []; let nextId 1; const TodoModel { findAll({ completed } {}) { if (completed undefined) return todos; return todos.filter(t t.completed completed); }, findById(id) { return todos.find(t t.id id); }, create({ title, description }) { const todo { id: nextId, title, description: description || , completed: false, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; todos.push(todo); return todo; }, update(id, data) { const todo this.findById(id); if (!todo) return null; Object.assign(todo, data, { updatedAt: new Date().toISOString() }); return todo; }, remove(id) { const index todos.findIndex(t t.id id); if (index -1) return false; todos.splice(index, 1); return true; } }; module.exports TodoModel;用内存存储的好处是零配置、启动即用而且你能清楚看到数据是怎么进怎么出的。等你把增删改查都跑通了再把这个文件换成数据库操作控制器层几乎不用动——这就是分层的价值。实操心得内存存储有个坑服务一重启数据就没了。开发阶段这其实是好事每次重启都是干净状态方便反复测试。但如果你想让数据持久化可以先用一个 JSON 文件读写来过渡比直接上数据库简单得多。3.4 控制器业务逻辑和参数校验放这里控制器是真正干活的地方。以创建待办为例我让 AI 生成的逻辑包含参数校验、调用模型、返回统一格式// controllers/todoController.js const TodoModel require(../models/todoModel); const { success, fail } require(../utils/response); exports.createTodo (req, res, next) { try { const { title, description } req.body; if (!title || typeof title ! string || title.trim() ) { return res.status(400).json(fail(40001, 标题不能为空)); } const todo TodoModel.create({ title: title.trim(), description }); res.status(201).json(success(todo)); } catch (err) { next(err); // 交给统一错误处理 } };这里有几个我特别在意的点。第一参数校验必须在控制器里做不能指望前端传对。第二title.trim()去掉首尾空格避免用户输入一堆空格绕过非空校验。第三用next(err)把异常抛给统一错误处理而不是每个地方都写一遍res.status(500)。查询单条待办时id 的类型转换是个容易翻车的地方。URL 里的 id 是字符串而内存里存的 id 是数字直接比较会永远不相等。所以要先转换exports.getTodo (req, res, next) { const id Number(req.params.id); if (Number.isNaN(id)) { return res.status(400).json(fail(40002, id 必须是数字)); } const todo TodoModel.findById(id); if (!todo) { return res.status(404).json(fail(40401, 待办不存在)); } res.json(success(todo)); };Number.isNaN这个判断很关键。JavaScript 里Number(abc)返回NaN而NaN NaN是false所以不能用判断必须用Number.isNaN()。这类细节 AI 有时候会漏你得自己补上。3.5 统一响应和错误处理让代码干净的关键utils/response.js就两个函数但能让所有控制器保持一致exports.success (data null, message success) ({ code: 0, message, data }); exports.fail (code, message) ({ code, message, data: null });错误处理中间件则负责兜底// middlewares/errorHandler.js module.exports (err, req, res, next) { console.error([Error], err.message); res.status(500).json({ code: 50000, message: 服务器内部错误, data: null }); };注意错误处理中间件里千万不要把err.stack直接返回给客户端那会暴露你的文件路径和代码结构是安全隐患。日志里打出来自己看就行返回给用户的永远是笼统的提示。4. 完整实操流程从零到接口跑通4.1 分步搭建的完整命令清单把上面的内容串起来实际操作流程是这样的。我按顺序列一遍你可以直接照着敲# 1. 创建项目并初始化 mkdir todo-api cd todo-api npm init -y # 2. 安装依赖 npm install express npm install --save-dev nodemon # 3. 创建目录结构 mkdir -p src/routes src/controllers src/models src/middlewares src/utils tests # 4. 依次创建文件内容见上文各节 # src/app.js # src/server.js # src/routes/todos.js # src/controllers/todoController.js # src/models/todoModel.js # src/middlewares/logger.js # src/middlewares/errorHandler.js # src/utils/response.js # 5. 启动开发服务 npm run dev路由文件routes/todos.js把 URL 和控制器函数对应起来const express require(express); const router express.Router(); const ctrl require(../controllers/todoController); router.get(/, ctrl.listTodos); router.get(/:id, ctrl.getTodo); router.post(/, ctrl.createTodo); router.put(/:id, ctrl.updateTodo); router.patch(/:id, ctrl.patchTodo); router.delete(/:id, ctrl.removeTodo); module.exports router;启动后看到“服务已启动监听端口 3000”就说明骨架搭好了。4.2 用 curl 逐个验证接口服务跑起来只是第一步接口能不能用还得实测。我习惯用 curl 一个个过比开 Postman 还快# 创建一条待办 curl -X POST http://localhost:3000/api/todos \ -H Content-Type: application/json \ -d {title:学习 Express,description:跑通 CRUD} # 查询全部 curl http://localhost:3000/api/todos # 按状态过滤 curl http://localhost:3000/api/todos?completedfalse # 查询单条 curl http://localhost:3000/api/todos/1 # 更新状态 curl -X PATCH http://localhost:3000/api/todos/1 \ -H Content-Type: application/json \ -d {completed:true} # 删除 curl -X DELETE http://localhost:3000/api/todos/1每一条命令执行完对照返回的 JSON 看code和data是否符合预期。这一步千万别偷懒我见过太多人代码写完不测结果上线才发现某个接口路径写错了。4.3 参数校验的边界情况怎么测接口能返回正确结果只是及格线真正体现功力的是边界情况。我整理了一份必测清单测试场景输入预期结果标题为空{title:}400提示标题不能为空标题全是空格{title: }400trim 后为空id 非数字/api/todos/abc400提示 id 必须是数字id 不存在/api/todos/9999404提示待办不存在请求体不是 JSON纯文本400Express 自动处理更新不存在的 idPUT/api/todos/9999404这些场景我都是让 AI 帮我列出来的它列得比我自己想得全。但测试执行必须自己来因为 AI 不知道你的实际代码有没有真的处理这些情况。有一次我让 AI 生成校验逻辑它写了判断但忘了 return结果校验失败后代码继续往下跑还是把空数据存进去了。这种 bug 只有实测才能发现。4.4 加一个请求日志中间件调试阶段有个日志中间件会省很多事。它能告诉你每个请求的方法、路径、耗时// middlewares/logger.js module.exports (req, res, next) { const start Date.now(); res.on(finish, () { const duration Date.now() - start; console.log(${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms); }); next(); };res.on(finish)是在响应发送完成后触发的这样能拿到准确的状态码和耗时。如果你在next()之前打印状态码还是默认的 200不准。这个细节 AI 第一次给我生成时也写错了我改成监听 finish 事件才对。5. 踩坑实录这些问题我替你踩过了5.1 AI 生成代码的典型问题清单用 AI 写代码这段时间我总结出它最容易出问题的几类地方你 review 的时候重点看这些问题类型具体表现我的处理方式忘记 return校验失败后代码继续执行每个分支都检查有没有 return类型不匹配字符串 id 和数字 id 比较显式转换并校验中间件顺序错错误处理写在路由前错误处理永远放最后异步错误没捕获async 函数里抛错没接住用 try/catch 包住或 next(err)硬编码敏感信息端口、密钥写死在代码里抽到 .env 用环境变量缺少边界校验只处理正常输入按上面的清单逐个补这些问题不是 AI 能力不行而是它生成的是“理想路径”下的代码边界情况需要你来补。把 AI 当成写初稿的人你来做终审效率最高。5.2 端口被占用怎么办启动服务时报EADDRINUSE意思是端口被占了。两个办法换端口或者找到占用进程杀掉。# 查看谁占用了 3000 端口macOS/Linux lsof -i :3000 # 杀掉对应进程 kill -9 PIDWindows 上用netstat -ano | findstr :3000找到 PID再taskkill /PID PID /F。我一般直接把端口改成环境变量在.env里写PORT3001避免和别的项目冲突。5.3 请求体解析失败的排查思路如果你 POST 请求返回的req.body是空的八成是这两个原因一是没加app.use(express.json())二是请求头没带Content-Type: application/json。前者是代码问题后者是调用问题。排查时先看日志中间件打出来的请求信息确认请求确实到了服务端再检查中间件有没有挂上。还有一种情况是 JSON 格式本身写错了比如多了个逗号、少了引号。Express 的 json 中间件遇到非法 JSON 会直接抛错被错误处理中间件捕获后返回 500。这时候去看服务端日志里的错误信息通常能直接定位到问题。5.4 关于“免费 API”和第三方服务的提醒搜索热词里出现了不少关于免费大模型 API、第三方接口调用的内容。这里我想提醒一句练手项目尽量用自己本地能跑通的东西。依赖外部 API 会引入网络、鉴权、额度限制等一堆变量一旦调不通你分不清是自己的代码问题还是对方服务的问题。等你把本地 API 服务跑熟了再去对接外部服务那时候你已经有能力快速定位问题了。如果你确实想在这个 Todo 服务里加一点 AI 能力比如自动给待办生成标签那也应该先把基础 CRUD 跑通再单独加一个接口去调用外部服务保持职责分离。这样出问题时能快速判断是哪一层的问题。6. 后续可以怎么扩展这个项目基础版跑通之后这个项目还有很多可以深挖的方向我按难度排个序你可以挑感兴趣的继续做。第一层加持久化。把内存数组换成 SQLite 或 JSON 文件读写理解数据持久化的基本思路。SQLite 不需要单独装服务一个文件就是一个数据库特别适合练手。第二层加测试。用 Jest 或 Mocha 写接口测试把上面那张边界情况表变成自动化测试用例。跑一次测试就能验证所有接口比手动 curl 高效得多。第三层加鉴权。给接口加上简单的 Token 校验理解中间件在请求链中的作用。这一步能让你明白为什么中间件顺序那么重要。第四层加文档。用 Swagger 或手写一份接口文档把每个接口的参数、返回值、错误码都写清楚。写文档的过程会逼你重新审视接口设计是否合理。第五层换框架对比。把 Express 换成 Fastify 重写一遍对比两者的路由写法、性能表现、插件机制。这种对比学习对理解框架设计理念特别有帮助。我个人在实际操作中的体会是小项目最大的价值不在于功能多完整而在于每个环节你都亲手走过一遍。用 AI 搭 API 服务这件事真正学到东西的时刻不是它帮你生成代码的那几秒而是你 review 代码、发现边界问题、动手修复的过程。AI 把重复劳动压缩了省下来的时间正好用来思考那些它替你想不到的地方。等你把这个 Todo 服务从内存版一路改到带测试、带鉴权、带文档的版本再回头看你对后端 API 的理解会比看十篇教程都扎实。