从零上手 Node.js:一个滴滴后端老油条的深夜教学
去年双11前夜,我还在深圳科技园的办公室里疯狂调接口。那天晚上凌晨两点,我们司机端的某个微服务突然崩了,日志里全是 Cannot read property 'id' of undefined —— 看着这熟悉的报错,我差点把咖啡泼到键盘上。作为一个在滴滴干了四年、负责过司机端核心业务的老后端,这种线上事故见得多了,但每次还是血压飙升。
其实那会儿问题根源很简单:前端用 React 写了个新页面,后端用 Node.js 搭了个临时接口,结果没做任何参数校验。产品经理急着上线“司机激励活动”,测试又说“逻辑没问题”,结果一上线就炸了。
后来我复盘的时候就在想:很多刚转全栈或者刚接触后端的同学,其实不是不会写代码,而是对服务器端 JavaScript 的整个运行机制、工程规范、错误处理缺乏系统认知。于是今天这篇教程,就当是给当年那个加班到崩溃的自己一点安慰吧——也顺便帮帮那些正在被 Node.js 折磨的新手。
为什么 Node.js 值得学?
我知道很多人第一反应是:“前端不是有 React/Vue 就够了吗?干嘛还要碰后端?”
但现实很骨感。在腾讯系扎堆的深圳,全栈能力几乎成了标配。我们团队现在搞新项目,基本都是 React + Node.js 全家桶。前端同学要能看懂 API 路由怎么配,后端也要知道前端传参的结构。而且说实话,Node.js 上手真的快——你只要会点 JavaScript,搭个 HTTP 服务十分钟就能跑起来。
上周五我带实习生小张写个内部工具,他之前只写过 React 组件。我让他试试用 Express 写个 mock 接口,结果他二十分钟就搞定了 GET/POST,还顺手加了 CORS。那一刻我仿佛看到了四年前刚进滴滴的自己:原来后端也没那么可怕。
第一步:别直接 npm install -g
新手最容易犯的错误,就是跟着网上教程一顿 npm install -g express,然后发现全局装了一堆包,版本混乱,项目一换环境就跑不起来。
血泪教训:永远用本地依赖!
mkdir my-node-app
cd my-node-app
npm init -y
npm install express
然后建个 index.js:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, 深圳打工人!');
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
运行 node index.js,浏览器打开 http://localhost:3000,搞定!
💡 开发心得:我习惯在
package.json里加个"start": "node index.js",这样以后直接npm start就行,不用记命令。别小看这点细节,线上部署时运维同学会感谢你。
处理请求:别再裸奔了
很多教程到这里就结束了,但真实场景远不止返回一句字符串。比如我们司机端有个接口 /api/driver/profile,前端(通常是 React App)会带着 driverId 过来查信息。
新手常这么写:
app.get('/api/driver/profile', (req, res) => {
const driverId = req.query.driverId;
// 直接去数据库查...
});
大忌!如果前端忘了传 driverId,或者传了个 "abc",你的服务就可能挂掉。所以必须做输入校验。
推荐用 joi 或 zod 做 schema 校验。我司现在主推 zod,因为 TypeScript 友好:
const { z } = require('zod');
const profileQuerySchema = z.object({
driverId: z.string().regex(/^\d+$/).min(1)
});
app.get('/api/driver/profile', (req, res) => {
try {
const { driverId } = profileQuerySchema.parse(req.query);
// 安全地使用 driverId
res.json({ success: true, data: { name: '张师傅' } });
} catch (error) {
res.status(400).json({ error: error.errors });
}
});
这样即使前端传错参数,你也只会返回 400,而不是让整个进程 crash。
🤯 真实场景:去年我们有个接口没校验手机号格式,结果测试同学输了个
"null"字符串,导致短信平台扣费异常。那次事故后,团队强制要求所有入参必须 schema 校验。
和 React 配合:CORS 与代理
如果你正在用 Create React App 开发前端,访问本地 Node.js 服务大概率会遇到 CORS 错误:
Access to fetch at 'http://localhost:3000/api/xxx' from origin 'http://localhost:3001' has been blocked by CORS policy.
别慌,这是浏览器的安全策略。解决方法有两个:
方案一:后端开 CORS(适合独立部署)
const cors = require('cors');
app.use(cors()); // 开发环境可以这么简单粗暴
但生产环境千万别这么干!应该指定具体域名:
app.use(cors({
origin: ['https://your-react-app.com']
}));
方案二:前端开发时用 proxy(推荐)
在 React 项目的 package.json 里加:
{
"name": "my-react-app",
"proxy": "http://localhost:3000"
}
这样你在 React 里写 fetch('/api/driver/profile'),开发服务器会自动代理到 http://localhost:3000/api/...,完美绕过 CORS。
😅 吐槽时间:我们组有个前端兄弟,死活不肯配 proxy,非要在每个 fetch 里写完整 URL。结果某天他改了本地端口,所有接口 404,debug 了俩小时。从此我们文档里加粗写着:“请用 proxy,不然请你喝瑞幸”。
自动重启:告别手动 Ctrl+C
你肯定经历过:改一行代码,就得手动停掉 Node 进程再重启。烦死了!
解决方案:用 nodemon。
npm install -D nodemon
然后改 package.json:
{
"scripts": {
"dev": "nodemon index.js"
}
}
现在运行 npm run dev,代码一保存,服务自动重启。幸福感拉满。
🔧 开发心得:我习惯在
.gitignore里加上node_modules/和*.log,但不要忽略package-lock.json!否则队友拉代码后依赖版本不一致,又是线上事故的开始。这点在滴滴吃过亏,现在成了团队硬性规范。
错误处理:别让进程挂了
Node.js 是单线程的(虽然底层 libuv 是多线程),一旦 uncaught exception,整个进程就退出了。在微服务架构里,这意味着这个实例下线,流量打到其他实例,可能引发雪崩。
所以必须全局兜底:
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
// 记录日志、告警...
process.exit(1); // 优雅退出,让 PM2 重启
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});
但更好的做法是用中间件统一处理错误:
// 错误处理中间件(必须放在最后)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: '服务器开小差了,请稍后再试' });
});
配合 async/await 时,记得用 try/catch,或者用 express-async-errors 这类库自动捕获 Promise reject。
项目结构:别把所有代码塞进 index.js
我见过太多新手把路由、数据库、业务逻辑全写在一个文件里。短期看没问题,但项目一复杂就变成意大利面条。
建议按功能拆分:
my-node-app/
├── src/
│ ├── routes/
│ │ └── driverRoutes.js
│ ├── controllers/
│ │ └── driverController.js
│ ├── services/
│ │ └── driverService.js
│ └── utils/
│ └── logger.js
├── index.js
└── package.json
比如 driverRoutes.js:
const express = require('express');
const router = express.Router();
const { getDriverProfile } = require('../controllers/driverController');
router.get('/profile', getDriverProfile);
module.exports = router;
然后在 index.js 里挂载:
const driverRoutes = require('./src/routes/driverRoutes');
app.use('/api/driver', driverRoutes);
💬 团队经验:我们司机端项目现在有上百个接口,靠这套结构,新人三天就能上手改需求。代码可读性和可维护性,真的不是玄学,是工程纪律。
发布到 GitHub:别裸奔提交
写完代码,记得推到 GitHub。但有几个坑要注意:
- 别提交 node_modules:
.gitignore必须包含它。 - 别提交敏感配置:比如数据库密码,用
.env文件 +dotenv加载,并把.env加到.gitignore。 - 写个 README.md:哪怕只有三行,说明怎么安装、怎么启动。
一个合格的开源风格项目,README 至少包含:
# My Node.js App
## 安装
npm install
## 启动
npm run dev
## 接口文档
GET /api/driver/profile?driverId=123
🌟 个人习惯:我喜欢在 README 顶部加个 badge,比如 ``。虽然小项目用不上 CI,但看着专业(笑)。
最后:Node.js 不是银弹
说了这么多,也得泼点冷水。Node.js 适合 I/O 密集型任务(比如 API 网关、实时聊天),但不适合 CPU 密集型计算。如果你要做图像处理、大数据分析,还是乖乖用 Go 或 Python 吧。
另外,异步编程容易写出回调地狱。现在主流都用 async/await,但也要注意错误传播和并发控制。比如同时发起 100 个数据库查询,可能把 DB 打挂。这时候要用 p-limit 之类限制并发数。
结语
写这篇文章时,窗外深圳的夜雨刚停。我想起四年前第一次在滴滴写 Node.js 服务时,也是这样一个深夜。那时候连 middleware 是啥都不懂,现在却要 review 别人的 PR。
技术这东西,没有捷径。但只要你愿意动手、不怕踩坑、重视代码质量,Node.js 其实是个很友好的起点。尤其是当你已经会 React,前后端打通的感觉,真的很爽。
如果你照着这篇教程跑通了第一个服务,欢迎去 GitHub 给我点个 star(开玩笑的)。但真心希望你能少走点弯路——毕竟,我们程序员的时间,不该浪费在重复造轮子和 debug 低级错误上。
对了,下周我要开始研究 Bun 了。听说比 Node.js 快十倍?等我踩完坑再来分享。
| 对比项 | 新手常见做法 | 推荐做法 |
|---|---|---|
| 依赖管理 | 全局安装 (npm install -g) |
本地依赖 + lock 文件 |
| 参数校验 | 直接使用 req.query.xxx |
用 zod/joi 做 schema 校验 |
| 错误处理 | 不处理,进程 crash | 全局 uncaught + 中间件兜底 |
| 项目结构 | 所有代码塞进一个文件 | 按 routes/controllers/services 拆分 |
| 开发体验 | 手动重启 | 用 nodemon 自动重启 |
本文代码示例已整理到 GitHub:github.com/yourname/nodejs-beginner-demo(假装有链接)
实际项目可参考 Express 官方 examples,或者 NestJS 这类更企业级的框架。

评论 0