当前位置: 首页 > news >正文

【Node.js】实战:从 0 搭建一个任务管理 RESTful API(Node 22 + Express)】

从零搭建一个 Node.js 22 + Express 的任务管理 RESTful API,涵盖环境搭建、项目结构、路由设计、CRUD 实战、错误处理和测试。每个关键步骤都配图 + 可跑代码,适合直接照着敲一遍,然后发到 CSDN 当成自己的 Node.js 实战博文。

目录

一、为什么 2025 年还值得学 Node.js 实战?

二、实战目标:我们要搭什么?

三、整体流程一眼看完

四、环境准备与项目初始化

1. 确认 Node.js 版本(推荐 22 LTS)

2. 初始化项目

3. 安装依赖

五、项目结构:按“功能模块”组织

六、写入口:app.js

七、写路由:routes/tasks.js(核心 CRUD)

八、跑起来 + 用 curl 测试

1. 启动服务器

2. 测试 CRUD

九、加点“工程味”:错误处理 + 输入校验

1. 统一错误响应中间件

2. 更严格的参数校验

十、避坑指南:新手最容易踩的坑

十一、如何把这篇变成你自己的 CSDN 博文?


一、为什么 2025 年还值得学 Node.js 实战?

  • Node.js 22 已经进入 LTS,原生支持 TypeScript 执行、内置 WebSocket 客户端、require(esm).env文件等,很多以前必须装包才能干的事,现在运行时自己就能搞定mortexsolutions.com。
  • Express 依然是 2025 年最主流的 Node.js Web 框架之一,生态成熟、资料最多jeuxdevelopers.com+1。
  • RESTful API 仍然是后端最基础的“通用语言”,学会用 Node.js 搭一套 API,以后不管是接前端、写管理后台还是做微服务,都很有用jianshu.com。

二、实战目标:我们要搭什么?

我们要做一个“任务管理系统(Tasks API)”,功能包括:

  • 获取所有任务:GET /tasks
  • 获取单个任务:GET /tasks/:id
  • 创建新任务:POST /tasks
  • 更新任务:PUT /tasks/:id
  • 删除任务:DELETE /tasks/:id

数据先放内存(数组),不连数据库,方便专注理解 Node + Express 本身。

三、整体流程一眼看完

四、环境准备与项目初始化

1. 确认 Node.js 版本(推荐 22 LTS)

node -v # 建议看到 v22.x.x

如果版本太旧,去官网下载 22 LTS 安装aliyun.com。

2. 初始化项目

mkdir node-task-api cd node-task-api npm init -y

这会生成一个package.json,记录项目信息和依赖aliyun.com。

3. 安装依赖

npm install express npm install --save-dev nodemon
  • express:Web 框架,用来定义路由和中间件aliyun.com。
  • nodemon:开发时自动重启服务,改完代码不用自己Ctrl+C再启动aliyun.com。

package.jsonscripts里加一条:

"scripts": { "start": "nodemon app.js" }

以后只需要npm start就能跑开发服务器。

五、项目结构:按“功能模块”组织

2025 年比较推荐的 Node.js 项目结构是按功能分层,而不是所有代码塞进一个文件logrocket.com+2。

我们这次用一个简化版结构:

node-task-api/ ├── app.js # 入口:创建 Express 应用,加载路由 ├── package.json ├── package-lock.json └── routes/ └── tasks.js # 任务相关路由

💡 秘籍:随着项目变大,你还可以再加controllersservicesmodels等目录,把“路由 / 业务逻辑 / 数据访问”分层dev.to。

六、写入口:app.js

// app.js const express = require('express'); const tasksRoutes = require('./routes/tasks'); const app = express(); const PORT = process.env.PORT || 3000; // 1. 中间件:解析 JSON 请求体 app.use(express.json()); // 2. 简单日志中间件(自己写) app.use((req, res, next) => { const start = Date.now(); res.on('finish', () => { const cost = Date.now() - start; console.log(`${req.method}${req.url} - ${res.statusCode} -${cost}ms`); }); next(); }); // 3. 路由:/tasks 下的所有接口 app.use('/tasks', tasksRoutes); // 4. 启动服务器 app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });

这段代码做了几件事:

  • 创建 Express 应用
  • express.json()解析请求体,这样req.body才能拿到 JSON 数据aliyun.com
  • 加了一个简单的日志中间件,方便观察请求耗时
  • /tasks路由交给routes/tasks.js处理

七、写路由:routes/tasks.js(核心 CRUD)

// routes/tasks.js const express = require('express'); const router = express.Router(); // 示例数据(内存里的“数据库”) let tasks = [ { id: 1, title: 'Buy groceries', description: 'Milk, Cheese, Pizza, Fruit', done: false, }, { id: 2, title: 'Learn Node.js', description: 'Follow a Node.js tutorial and build a REST API', done: false, }, ]; let nextId = 3; // 获取所有任务 router.get('/', (req, res) => { res.json({ tasks }); }); // 获取单个任务 router.get('/:id', (req, res) => { const taskId = parseInt(req.params.id, 10); const task = tasks.find(t => t.id === taskId); if (!task) { return res.status(404).json({ message: 'Task not found' }); } res.json(task); }); // 创建新任务 router.post('/', (req, res) => { const { title, description } = req.body; if (!title) { return res.status(400).json({ message: 'title is required' }); } const newTask = { id: nextId++, title, description: description || '', done: false, }; tasks.push(newTask); res.status(201).json(newTask); }); // 更新任务 router.put('/:id', (req, res) => { const taskId = parseInt(req.params.id, 10); const task = tasks.find(t => t.id === taskId); if (!task) { return res.status(404).json({ message: 'Task not found' }); } const { title, description, done } = req.body; task.title = title || task.title; task.description = description || task.description; task.done = done !== undefined ? done : task.done; res.json(task); }); // 删除任务 router.delete('/:id', (req, res) => { const taskId = parseInt(req.params.id, 10); const task = tasks.find(t => t.id === taskId); if (!task) { return res.status(404).json({ message: 'Task not found' }); } const index = tasks.indexOf(task); tasks.splice(index, 1); res.json({ result: true }); }); module.exports = router;

这个文件的结构参考了阿里云社区一个 2024 年的 Node.js REST 教程,但做了更清晰的错误处理和参数校验aliyun.com。

八、跑起来 + 用 curl 测试

1. 启动服务器

npm start

终端会看到:

Server is running on port 3000

2. 测试 CRUD

获取所有任务:

curl http://localhost:3000/tasks

获取单个任务:

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

创建新任务:

curl -X POST \ -H "Content-Type: application/json" \ -d '{"title":"Learn Node.js"}' \ http://localhost:3000/tasks

更新任务:

curl -X PUT \ -H "Content-Type: application/json" \ -d '{"title":"Master Node.js", "done": true}' \ http://localhost:3000/tasks/3

删除任务:

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

如果你用 Postman,可以直接把这些 URL 和方法配置进去,更直观csdn.net。

九、加点“工程味”:错误处理 + 输入校验

2025 年的 Node.js API 实战里,输入校验 + 统一错误格式是基本要求dev.to。

1. 统一错误响应中间件

app.js里加:

// 统一错误响应格式 app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ code: 500, message: 'Internal Server Error', error: process.env.NODE_ENV === 'development' ? err.message : undefined, }); });

2. 更严格的参数校验

比如创建任务时,要求title必须是非空字符串:

router.post('/', (req, res) => { const { title, description } = req.body; if (!title || typeof title !== 'string' || !title.trim()) { return res.status(400).json({ code: 400, message: 'title must be a non-empty string', }); } // ... 创建任务逻辑 });

⚠️ 注意:永远不要信任客户端输入,这是 2025 年 API 安全的第一条原则dev.to。

十、避坑指南:新手最容易踩的坑

  1. 端口被占用:EADDRINUSE

    • 换一个端口:PORT=3001 npm start
    • 或者杀掉占用进程:lsof -i :3000(macOS/Linux)
  2. 请求体解析失败

    • 忘记app.use(express.json())req.bodyundefined
    • 忘记设置Content-Type: application/json→ 解析失败
  3. id 类型不一致

    • URL 里的req.params.id是字符串,要parseInt再和数字比较
    • 否则1 === '1'false,导致找不到任务
  4. 修改数据没重启服务

    • nodemon可以自动重启,避免手动重启
http://www.jsqmd.com/news/749893/

相关文章:

  • Warcraft Helper完整指南:3分钟解决魔兽争霸3现代系统兼容性问题
  • 基于GitHub Pages与VuePress构建个人技术博客全流程指南
  • GitHub贡献3D可视化:用Next.js与Three.js构建像素城市
  • 突破性解决方案:如何用ide-eval-resetter永久告别JetBrains IDE试用期限制
  • 魔兽争霸3终极优化方案:WarcraftHelper深度解析与实战指南
  • 终极解决Unity游戏语言障碍:XUnity.AutoTranslator智能翻译完整指南
  • 百度网盘直链解析:免费突破限速的终极指南
  • WAM-202601:Cosmos Policy02【微调训练数据构造方式:把非视频数据伪装成视频帧,插到原本视频帧序列之间,通过mask构造三类训练任务:①Policy训练、②WM训练、③VF训练】
  • 具身智能(38): ROS2介绍
  • 网盘下载加速终极方案:LinkSwift直链下载助手完全指南
  • Python原生基础设施即代码:Zeroclaw框架实践指南
  • 若依RuoYi框架项目结构深度解析:从ruoyi-admin到ruoyi-ui,新手如何快速上手?
  • 从多头到分组:图文拆解MQA/GQA如何让你的Llama 2模型‘瘦身’又提速
  • 自指螺旋紧致度与精细结构常数的完整推导(世毫九实验室严禁学术剽窃)
  • 云原生内存管理插件:MemOS-Cloud-OpenClaw-Plugin深度解析
  • DeepSeek V4最大的遗憾
  • 容器化开发环境:使用Docker解决TranslucentTB项目协作难题的完整指南
  • 开源方案让老旧电视重获新生:MyTV-Android的技术救赎之路
  • Java 面试:从 Spring Boot 到微服务的实战问答
  • 【编程语言】深度解构编程语言核心:从二进制底层到多语言数据类型全景图
  • 具身智能(42):Holo Motion开源模型
  • 如何彻底解决微信消息撤回困扰:Mac用户的终极消息保护方案
  • 3步解密:微信聊天记录恢复的终极解决方案
  • HPH核心构造一探究竟!看完秒变专家懂均质
  • 如何让老旧电视重获新生:MyTV-Android原生电视直播应用完全指南
  • OpenAI参与,重卷ImageNet:终于把FID做成训练
  • 自主AI代理的监管挑战与欧盟AI法案解析
  • 第六周周报
  • 从零开始的指针探索之旅1(C语言)
  • 瑞祥商联卡变现攻略:一分钟了解最佳平台选择! - 团团收购物卡回收