从零上手 Node.js:一个滴滴后端老油条的深夜教学

开源搬砖工
2025-12-24 02:58
阅读 13014

去年双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。但有几个坑要注意:

  1. 别提交 node_modules:.gitignore 必须包含它。
  2. 别提交敏感配置:比如数据库密码,用 .env 文件 + dotenv 加载,并把 .env 加到 .gitignore。
  3. 写个 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

最热最新
暂无评论
开源搬砖工Lv.1
0
影响力
0
文章
0
粉丝