Node.js + Express 实战:从零搭建天气查询 API 服务
1. 项目缘起与整体设计思路1.1 为什么选这个题目练手我一直觉得学一门技术最快的路径不是看文档而是动手做一个能跑起来的东西。API 服务就是这样一个绝佳的练手项目——它足够小小到一个人一两天就能搞定又足够完整完整到能覆盖后端开发的核心链路接收请求、处理逻辑、返回响应、错误处理、日志记录。这次我选的技术栈是Node.js Express。原因很直接JavaScript 一门语言从前端写到后端不用切换思维Express 的生态成熟到几乎任何需求都能找到现成的中间件再加上现在有 AI 辅助编码很多样板代码可以直接生成省下来的时间可以花在真正需要思考的架构设计上。这个项目适合谁如果你已经会一点 JavaScript 基础语法知道什么是函数、什么是对象但从来没自己从零搭过一个后端服务那这篇内容就是写给你的。如果你已经写过 Express 但一直是复制粘贴别人的代码不清楚每一行在干什么那这篇也能帮你把知识串起来。1.2 这个 API 服务到底要做什么我给自己定的目标很明确做一个天气查询 API 服务。用户传一个城市名服务返回这个城市的天气信息。听起来简单但麻雀虽小五脏俱全需要一个 HTTP 服务器接收请求需要路由来区分不同的接口需要参数校验防止用户传乱七八糟的东西需要调用外部数据源获取天气需要统一的响应格式需要错误处理不能一报错就崩需要日志方便排查问题这七个需求基本上就是一个生产级 API 服务的骨架。把这个项目吃透以后换任何业务场景套路都是一样的。1.3 技术选型的几个关键决策为什么用 Express 而不是 Fastify这个问题我被问过很多次。Fastify 性能确实更好基准测试数据摆在那里。但对于小项目来说Express 的优势在于中间件生态最丰富、文档最全、遇到问题搜一下就有答案。Fastify 的插件体系虽然设计得更现代但学习曲线更陡。我的建议是先把 Express 用熟理解 HTTP 服务的本质再去尝试 Fastify 不迟。为什么不用 TypeScript小项目实战的目的是快速验证想法TypeScript 的类型定义在项目初期反而是一种负担。等你把业务逻辑跑通了再迁移到 TypeScript 也不迟。当然如果你已经熟悉 TypeScript直接用也没问题。AI 在这个项目里扮演什么角色我的用法是让 AI 生成样板代码和重复性逻辑比如路由注册、错误处理中间件、参数校验规则。但核心的业务逻辑和架构决策必须自己来。AI 生成的代码你要能看懂、能改、能调试否则出了问题你连从哪下手都不知道。2. 环境搭建与项目初始化2.1 Node.js 安装的坑与正确姿势Node.js 的安装看起来简单但版本选择有讲究。我推荐用LTS 版本长期支持版不要追最新的 Current 版本。LTS 版本经过充分测试生态兼容性最好。截至我写这篇内容的时候Node.js 20.x 和 22.x 都是 LTS选哪个都行。安装方式我强烈建议用nvmNode Version Manager而不是直接去官网下载安装包。原因很简单不同项目可能依赖不同的 Node.js 版本nvm 让你可以在版本之间一键切换。Windows 用户可以用 nvm-windowsMac 和 Linux 用户直接用官方的 nvm 脚本。安装完成后打开终端验证一下node -v npm -v两个命令都能输出版本号说明安装成功。如果提示“command not found”大概率是环境变量没配好检查一下 nvm 的安装路径是否加到了 PATH 里。注意不要用 sudo 安装全局 npm 包这会导致权限问题。如果遇到权限报错正确做法是配置 npm 的全局目录到用户目录下而不是加 sudo。2.2 项目目录结构设计很多人写小项目习惯把所有代码塞进一个index.js一开始确实爽但改到第三天就痛苦了。我建议从一开始就按职责分目录weather-api/ ├── src/ │ ├── routes/ # 路由定义 │ │ └── weather.js │ ├── controllers/ # 业务逻辑 │ │ └── weatherController.js │ ├── services/ # 外部服务调用 │ │ └── weatherService.js │ ├── middlewares/ # 中间件 │ │ ├── errorHandler.js │ │ └── requestLogger.js │ ├── utils/ # 工具函数 │ │ └── response.js │ └── app.js # Express 应用配置 ├── .env # 环境变量 ├── .gitignore ├── package.json └── server.js # 入口文件这个结构的好处是路由只管 URL 和 HTTP 方法的映射控制器管业务逻辑服务层管数据获取。三层各司其职以后要换数据源只改服务层要加新接口只加路由和控制器。2.3 初始化项目与依赖安装mkdir weather-api cd weather-api npm init -y npm install express dotenv axios npm install -D nodemon这里解释一下每个依赖的作用expressWeb 框架处理 HTTP 请求的核心dotenv读取.env文件里的环境变量比如端口号、API 密钥axios发 HTTP 请求用来调用外部天气数据接口nodemon开发依赖监听文件变化自动重启服务开发时必备在package.json里加两个脚本{ scripts: { start: node server.js, dev: nodemon server.js } }开发时用npm run dev部署时用npm start。3. 核心代码实现与关键细节3.1 入口文件与 Express 应用分离很多教程把app.listen()直接写在app.js里我不推荐这种做法。把应用配置和启动逻辑分开好处是测试的时候可以直接导入 app 而不启动服务器。server.js只做一件事——启动服务const app require(./src/app); const PORT process.env.PORT || 3000; app.listen(PORT, () { console.log(服务已启动监听端口 ${PORT}); });src/app.js负责组装中间件和路由const express require(express); const requestLogger require(./middlewares/requestLogger); const errorHandler require(./middlewares/errorHandler); const weatherRoutes require(./routes/weather); const app express(); app.use(express.json()); app.use(requestLogger); app.use(/api/weather, weatherRoutes); app.use(errorHandler); module.exports app;注意中间件的顺序express.json()必须在路由之前否则req.body拿不到数据错误处理中间件必须在所有路由之后否则捕获不到路由里抛出的错误。3.2 路由层只做映射不写逻辑路由层的职责非常单一把 URL 和 HTTP 方法映射到对应的控制器函数。不要在路由里写业务逻辑这是新手最容易犯的错误。const express require(express); const router express.Router(); const weatherController require(../controllers/weatherController); router.get(/:city, weatherController.getWeatherByCity); router.get(/:city/forecast, weatherController.getForecast); module.exports router;这里定义了两个接口GET /api/weather/:city查当前天气GET /api/weather/:city/forecast查未来几天预报。:city是路径参数Express 会自动把它解析到req.params.city。3.3 控制器层参数校验与响应组装控制器是业务逻辑的入口它要做三件事校验参数、调用服务层、组装响应。const weatherService require(../services/weatherService); const { success, error } require(../utils/response); async function getWeatherByCity(req, res, next) { try { const { city } req.params; if (!city || city.trim().length 0) { return res.status(400).json(error(城市名不能为空)); } if (city.length 50) { return res.status(400).json(error(城市名过长)); } const weatherData await weatherService.fetchWeather(city); res.json(success(weatherData)); } catch (err) { next(err); } } module.exports { getWeatherByCity, getForecast };几个关键点参数校验要前置。不要等到调用外部服务了才发现参数不对那样浪费一次网络请求。校验规则要具体比如城市名长度限制、特殊字符过滤。用next(err)传递错误。在 async 函数里throw的错误不会自动被 Express 捕获必须手动传给next()。这是 Express 的一个经典坑很多人在这里栽过跟头。响应格式要统一。我定义了一个response.js工具function success(data, message ok) { return { code: 0, message, data }; } function error(message, code 1) { return { code, message, data: null }; }这样前端拿到响应后只需要判断code是否为 0不用去猜每个接口的返回结构。3.4 服务层外部数据获取与容错服务层负责真正去拿数据。我用的是一个公开的天气数据接口通过 axios 调用const axios require(axios); const API_BASE process.env.WEATHER_API_BASE; const API_KEY process.env.WEATHER_API_KEY; async function fetchWeather(city) { const url ${API_BASE}/current.json; const params { key: API_KEY, q: city, lang: zh }; const response await axios.get(url, { params, timeout: 5000 }); return { city: response.data.location.name, temperature: response.data.current.temp_c, condition: response.data.current.condition.text, humidity: response.data.current.humidity, windSpeed: response.data.current.wind_kph, updatedAt: response.data.current.last_updated }; } module.exports { fetchWeather };这里有几个实战经验一定要设 timeout。不设超时的话外部接口挂了你的服务也会跟着挂请求会一直挂在那里直到客户端超时。5 秒是个合理的值。返回数据要裁剪。外部接口返回的字段可能几十个但你只需要其中几个。在服务层就把数据裁剪成你需要的结构控制器和前端都不用关心原始数据结构。API 密钥放环境变量。绝对不要把密钥硬编码在代码里然后提交到代码仓库。.env文件要加到.gitignore里。3.5 中间件日志与错误处理请求日志中间件记录每个请求的方法、路径、耗时function requestLogger(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()前打印是因为要等响应完成才能拿到状态码和耗时。全局错误处理中间件是最后一道防线function errorHandler(err, req, res, next) { console.error(未捕获错误:, err.message); if (err.code ECONNABORTED) { return res.status(504).json({ code: 1, message: 外部服务超时 }); } if (err.response err.response.status 404) { return res.status(404).json({ code: 1, message: 城市不存在 }); } res.status(500).json({ code: 1, message: 服务器内部错误 }); }错误处理中间件必须接收四个参数(err, req, res, next)少一个 Express 就不会把它当作错误处理中间件。这个细节很多人不知道。4. 常见问题排查与避坑指南4.1 端口被占用怎么办开发时经常遇到EADDRINUSE错误意思是端口已经被别的程序占了。两个解决办法# Mac/Linux 查看谁占了 3000 端口 lsof -i :3000 # Windows netstat -ano | findstr :3000找到进程号后 kill 掉或者直接换个端口。我习惯在.env里配PORT3001避免和常用端口冲突。4.2 async 错误没被捕获这是 Express 最经典的坑。看这段代码app.get(/test, async (req, res) { throw new Error(出错了); // 这个错误不会被错误处理中间件捕获 });async 函数返回的是一个 PromiseExpress 4.x 不会自动捕获 Promise 的 rejection。解决办法有三种手动 try-catch 然后next(err)、用express-async-errors这个包、或者升级到 Express 5Express 5 原生支持 async 错误捕获。我推荐第一种最可控。4.3 跨域问题前端调用接口时报 CORS 错误解决办法是加cors中间件npm install corsconst cors require(cors); app.use(cors());开发阶段可以允许所有来源生产环境要配置白名单只允许你自己的域名访问。4.4 常见问题速查表问题现象可能原因解决方法Cannot GET /api/weather路由路径不匹配检查路由注册的前缀和请求路径req.body为 undefined没加express.json()在路由之前注册 body 解析中间件错误处理中间件不生效参数不是四个确保是(err, req, res, next)外部接口调用超时没设 timeoutaxios 配置里加timeout: 5000环境变量读不到.env没加载入口文件顶部加require(dotenv).config()修改代码不生效没重启服务用 nodemon 启动或手动重启4.5 几个我踩过的坑dotenv 的加载时机。require(dotenv).config()必须放在最顶部在所有其他 require 之前。因为其他模块可能在加载时就会读取环境变量如果 dotenv 还没执行读到的就是 undefined。路径参数的编码问题。城市名如果包含中文或空格URL 里会被编码。Express 会自动解码req.params但如果你手动拼接 URL 去调外部接口记得用encodeURIComponent()处理。JSON 响应里的中文。Express 默认的res.json()会正确设置Content-Type: application/json; charsetutf-8中文不会乱码。但如果你用res.send()返回对象Express 也会自动转 JSON效果一样。5. 用 AI 辅助开发的正确姿势5.1 AI 能帮你做什么在这个项目里我用 AI 做了这些事生成路由和控制器的样板代码我只需要改业务逻辑写参数校验的正则表达式比如城市名只允许中文、英文和空格生成错误处理的分类逻辑把不同的错误码映射到不同的 HTTP 状态码写单元测试的用例覆盖正常和异常场景AI 生成的代码质量参差不齐关键是要能看懂。看不懂的代码不要用让 AI 解释一遍理解了再决定要不要。5.2 AI 不能替你做什么架构决策、错误处理的边界条件、业务逻辑的细节这些必须自己来。比如“城市名传空字符串应该返回 400 还是 404”这种问题没有标准答案取决于你的 API 设计约定。AI 会给你一个答案但不一定是你想要的。还有一个重要的点AI 生成的代码可能有安全漏洞。比如它可能会把用户输入直接拼接到 SQL 里或者忘记做输入过滤。安全相关的代码一定要自己审查。5.3 我的 AI 协作流程我的习惯是先自己想清楚要做什么用注释把逻辑写出来然后让 AI 把注释翻译成代码。这样 AI 是在执行我的设计而不是替我做设计。代码生成后我会逐行审查改掉不合理的部分加上自己的错误处理和日志。这个流程的好处是AI 提高了编码速度但架构和逻辑仍然在我掌控之中。出了问题我知道去哪找因为代码是我设计的。6. 部署与后续扩展6.1 本地验证清单部署之前我会用 curl 把每个接口都过一遍# 正常请求 curl http://localhost:3000/api/weather/beijing # 空城市名 curl http://localhost:3000/api/weather/ # 不存在的城市 curl http://localhost:3000/api/weather/notacity # 超长城市名 curl http://localhost:3000/api/weather/aaaaaaaaaa...每个接口的正常和异常路径都要覆盖确认返回的状态码和响应格式符合预期。6.2 可以继续扩展的方向这个项目跑通之后可以往几个方向扩展加缓存。天气数据不需要每次请求都去调外部接口可以加一层内存缓存比如 10 分钟内同一个城市的请求直接返回缓存结果。用node-cache这个包几行代码就能搞定。加限流。防止有人恶意刷接口用express-rate-limit限制每个 IP 的请求频率。加接口文档。用 Swagger 自动生成 API 文档前端同事不用问你接口怎么调。加健康检查接口。GET /health返回服务状态部署到云平台后负载均衡器会定期检查这个接口。迁移到 TypeScript。业务逻辑稳定后加上类型定义重构时更有底气。6.3 我个人的体会搭这个 API 服务最大的收获不是学会了 Express 的 API而是理解了一个后端服务的完整生命周期请求进来、经过中间件、到达路由、执行逻辑、调用外部服务、组装响应、返回给客户端、记录日志。这个流程走一遍以后看任何后端框架的文档都能快速上手因为概念是相通的。另外AI 辅助编码确实能提速但前提是你自己得有判断力。AI 给的代码对不对、好不好、安不安全你得能看出来。这个判断力来自你亲手写过的代码量没有捷径。最后分享一个小技巧每次改完代码不要只测你改的那个功能把相关的接口都跑一遍。我遇到过好几次改 A 功能把 B 功能改坏的情况都是因为只测了改动点。养成回归测试的习惯能省掉很多线上排查的时间。

相关新闻

openrig:多AI编码代理并行运行环境搭建与冲突排查

openrig:多AI编码代理并行运行环境搭建与冲突排查

1. 从 openrig 这个名字说起:它到底想解决什么问题第一次看到openrig这个词,我脑子里蹦出来的不是某个具体工具,而是一种"把散装零件拼成一台整机"的直觉。rig 在英文里本意是"装配、索具、钻井平台",在开发者…

2026/10/14 17:40:46 阅读更多 →
电力系统低碳调度与源荷不确定性建模:MATLAB+yalmip求解风电接入优化问题

电力系统低碳调度与源荷不确定性建模:MATLAB+yalmip求解风电接入优化问题

MATLAB代码:电力系统低碳调度,源荷不确定性与风电接入——从建模思路到yalmip落地去年我做了一个含风电接入的电力系统低碳调度项目,代码用MATLAByalmip实现。接手之前我以为最大的麻烦会是碳约束和风电随机性怎么建模,真正动手之…

2026/10/9 18:02:38 阅读更多 →
Win11误删字体自救:从字体包恢复到缓存重建

Win11误删字体自救:从字体包恢复到缓存重建

简介:一份面向Windows 11用户的字体修复资源包,专门解决误删系统字体后界面文字模糊、错位或图标显示异常等问题,适合普通用户和系统维护人员快速恢复默认字体环境。包内共347个文件,包括192个fon位图字体(用于系统控制…

2026/10/15 11:00:12 阅读更多 →

最新新闻

Transformer模型详解:从注意力公式到能跑通的极简实现

Transformer模型详解:从注意力公式到能跑通的极简实现

简介:这份《Transformer 模型详解》PDF 面向自然语言处理初学者、深度学习入门者及需要系统梳理注意力机制的算法工程师,帮助读者从零理解 Transformer 的整体架构与核心原理。资源为单个 PDF 文件,压缩包约 2.01MB,内容以图文结合…

2026/10/17 17:27:23 阅读更多 →
Vue项目三步部署指南:Ubuntu + Nginx从构建到线上发布

Vue项目三步部署指南:Ubuntu + Nginx从构建到线上发布

看到标题里的“ubnutu”,我先说个小事:这应该是 ubuntu 的笔误。不过这并不影响思路,下面我统一用 ubuntu 22.04 来讲。很多前端同学开发完 vue 项目,npm run build 一执行,dist 目录出来了,然后就卡住了—…

2026/10/17 17:27:23 阅读更多 →
内置ALC的音频前置放大器D2538A和D3308

内置ALC的音频前置放大器D2538A和D3308

应用领域D2538A和D3308是两款内置ALC(音频限幅器)的前置音频放大器芯片,其中D2538A为单通道,D3308为双通道,它特别适用于胎心仪、个人医疗防护、立体声收录机、盒式录音机等涉及音频放大与限幅的产品方案。功能介绍音频…

2026/10/17 17:26:23 阅读更多 →
FAIR风险量化实战:从模糊判断到可校准的年化损失建模

FAIR风险量化实战:从模糊判断到可校准的年化损失建模

简介:本资源是一份面向信息安全从业者、风控工程师与技术决策者的FAIR风险量化分析实战指南,聚焦解决企业级信息风险难以精准建模与量化评估的痛点。文档系统拆解FAIR框架核心要素(威胁源、脆弱性、资产、影响)、风险计算模型、蒙…

2026/10/17 17:26:23 阅读更多 →
CISSP 备考指南:八大知识域拆解与易错点复习清单

CISSP 备考指南:八大知识域拆解与易错点复习清单

简介:CISSP官方学习手册第九版是面向信息安全从业者及备考CISSP认证考生的权威参考资料,由信息安全领域专家编写,系统覆盖安全与风险管理、资产安全、安全工程、通信与网络安全、身份与访问管理、安全评估与测试、安全运营及软件开发安全等知…

2026/10/17 17:26:23 阅读更多 →
Flink + Iceberg 本地环境搭建实战:版本选型、Catalog 配置与常见问题排查

Flink + Iceberg 本地环境搭建实战:版本选型、Catalog 配置与常见问题排查

1. 为什么值得搭一套 Flink Iceberg 的本地环境大数据圈子里有个很现实的现象:很多团队用着 Hive 或者纯对象存储做数据湖,白天跑批还凑合,一到近实时场景就露馅——写入延迟高、小文件泛滥、Schema 变更要停服、回溯历史数据得全量重跑。Fl…

2026/10/17 17:26:23 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →