Node.js新手教程:从零开始学习服务器端JavaScript
上周五晚上10点半,办公室里只剩我和隔壁组的运维小哥。我盯着屏幕上那行经典的 Error: Cannot find module 'express',心里默默问候了下自己——早知道当初当产品经理的时候就该多敲点代码,也不至于现在被新来的Tech Lead安排重构一个老Node服务时手忙脚乱。
说起来有点惭愧,我这个“前产品经理转技术”的斜杠青年,在我们组已经混了快两年了。虽然现在天天写代码、画架构图,但骨子里还是保留着一点产品思维——比如特别在意代码的可维护性、接口的语义清晰度,以及“这玩意儿上线后会不会半夜把我叫醒”。也正因为这段经历,我特别能理解那些刚从浏览器JS转向服务端开发的同学:你们不是一个人在战斗!
今天这篇教程,就是想带大家从零开始,用最贴近实战的方式上手Node.js。不讲虚的,全是我在项目中踩过的坑、总结出的最佳实践,还有那些让我又爱又恨的工具。毕竟,作为一个每天8点就坐在工位上开始干活的早起型选手,时间宝贵,咱们得高效地学、聪明地写。
为什么是现在?一个被逼出来的学习契机
去年双11前夕,我们团队接了个紧急需求:把原来用PHP写的用户行为日志收集接口迁移到Node.js,理由很现实——高并发、低延迟,而且团队里没人会PHP(别问,问就是历史遗留问题)。领导拍板:“你不是做过PM吗?逻辑应该清楚,你来牵头。”
我当时内心OS:我逻辑是清楚,但我连package.json都还没手写过啊!但谁让我是那个“转岗过来的”,总得证明自己不是来混日子的。于是,一个周末,三杯美式,外加无数次npm install失败后的崩溃,我硬是把第一个能跑的服务搭起来了。
结果呢?上线第一天,因为没处理好异步回调,导致数据库连接池爆了,凌晨三点被PagerDuty叫醒。那一刻,我发誓:Node.js的坑,必须一次性踩明白。
别一上来就npm install -g express
很多新手教程一上来就让你装Express,然后app.get('/', ...),跑起来就觉得“哇,我也会写后端了!”——醒醒,兄弟,这离生产环境还差十万八千里。
真正的工程化开发,第一步不是写业务逻辑,而是搭建可靠、可维护、可观测的基础骨架。下面是我现在每次开新项目必做的几件事:
1. 用现代Node + npm/yarn/pnpm
首先确认你的Node版本。截至2024年,强烈建议使用Node 18 LTS或Node 20。为什么?因为它们原生支持ES Modules(.mjs或"type": "module"),Promise化的API更完善,而且V8引擎性能更好。
# 检查版本
node -v # 应该 >= v18.17.0
# 推荐用 nvm 管理多版本
nvm install --lts
nvm use --lts
关于包管理器,我个人现在用 pnpm。速度快、节省磁盘空间、依赖结构更扁平(避免node_modules嵌套地狱)。当然,如果你公司强制用yarn,那也行,但千万别再用老旧的npm 6了。
# 初始化项目
pnpm init -y
然后在package.json里加上:
{
"type": "module",
"scripts": {
"dev": "node --watch src/index.js",
"start": "node src/index.js"
}
}
注意那个--watch,这是Node 18.11+自带的文件监听重启功能,不用再装nodemon了(虽然它依然好用)。
2. 目录结构:别把所有代码塞进一个文件
我见过太多新手把路由、数据库、业务逻辑全写在一个server.js里。这不是写demo,这是给未来的自己埋雷。
参考我们团队的标准结构:
my-node-app/
├── src/
│ ├── index.js # 入口
│ ├── app.js # Express/Koa实例
│ ├── routes/ # 路由定义
│ ├── controllers/ # 业务逻辑
│ ├── services/ # 数据操作、第三方调用
│ ├── models/ # 数据模型(如果用ORM)
│ ├── utils/ # 工具函数
│ └── config/ # 配置文件
├── tests/ # 测试
├── .env # 环境变量(别提交!)
├── .gitignore
└── package.json
这种分层结构,哪怕项目规模翻十倍,你也能快速定位问题。而且,前端同学看了也会觉得亲切——是不是很像Vue/React的src目录?
3. 环境配置:别把密钥写死在代码里
还记得那次线上事故吗?就是因为有人把数据库密码直接写在config.js里,结果git push到公开仓库……运维差点拿拖鞋抽我。
正确做法:用 .env 文件 + dotenv 包。
pnpm add dotenv
// src/config/env.js
import dotenv from 'dotenv';
import path from 'path';
// 根据 NODE_ENV 加载不同 .env 文件
const envFile = `.env.${process.env.NODE_ENV || 'development'}`;
dotenv.config({ path: path.resolve(process.cwd(), envFile) });
export const config = {
port: process.env.PORT || 3000,
dbUrl: process.env.DATABASE_URL,
jwtSecret: process.env.JWT_SECRET,
};
然后在 .gitignore 里加上:
.env*
!/.env.example
对,留一个 .env.example 作为模板,但不包含真实值。
写代码:优雅地处理异步,别让回调地狱毁掉你
Node.js的核心优势是事件驱动、非阻塞I/O。但新手最容易栽在异步控制流上。
以前我们用回调(callback),后来用Promise,现在当然是 async/await 天下第一。但即便如此,错误处理还是容易翻车。
错误示范:
// 别这么干!
app.get('/user/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});
如果 findById 抛异常(比如数据库挂了),整个进程可能崩掉——Express 默认不会捕获 async 函数里的异常!
正确姿势:统一错误处理中间件
// src/middleware/errorHandler.js
export const errorHandler = (err, req, res, next) => {
console.error('Error:', err.stack);
// 生产环境不暴露堆栈
const status = err.status || 500;
const message = process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: err.message;
res.status(status).json({ error: message });
};
// 在 app.js 最后 use 它
app.use(errorHandler);
但光这样还不够,每个 async controller 还要手动 try/catch?太啰嗦!
于是我们团队封装了一个 asyncWrapper:
// src/utils/asyncWrapper.js
export const asyncWrapper = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
用起来超清爽:
// src/controllers/userController.js
import { asyncWrapper } from '../utils/asyncWrapper.js';
export const getUser = asyncWrapper(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
// 自定义错误,带上状态码
throw createHttpError(404, 'User not found');
}
res.json(user);
});
// 路由里
router.get('/:id', getUser);
小贴士:搭配
http-errors包,可以轻松抛出带状态码的错误,比如createHttpError(400, 'Invalid input')。
工具链:提升效率的利器
光会写代码不够,高效的开发者都有一套趁手的工具。分享几个我每天离不开的:
| 工具 | 用途 | 为什么推荐 |
|---|---|---|
| VS Code + REST Client | 调试API | 不用开Postman,.http文件里直接写请求,一键发送 |
| Prisma | ORM | 类型安全、自动迁移、查询构建器超直观 |
| Pino | 日志 | 性能比winston高3倍,JSON格式天然适配ELK |
| Jest | 测试 | 零配置、快照测试、Mock方便 |
| ESLint + Prettier | 代码规范 | 团队协作必备,避免“括号放哪”的圣战 |
举个例子,用 Prisma 替代手写SQL:
// prisma/schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
生成客户端后:
// 查询用户
const user = await prisma.user.findUnique({
where: { email: 'alice@example.com' },
});
类型提示直接给你推出来,连文档都不用查。这就是现代Node开发的体验。
前端视角:别忘了你的用户(和你的同事)
虽然写的是后端,但别忘了你曾经是个PM,现在也天天和前端打交道。所以,API设计要有同理心。
- 返回统一的数据结构:
{ data: ..., code: 200, message: 'success' } - 错误信息要明确:别返回
{ error: "Something went wrong" } - 支持CORS:开发时跨域是常态,记得配:
import cors from 'cors'; app.use(cors()); // 生产环境要限制 origin - 文档!文档!文档!用 Swagger 或 TS 注释生成,别让前端猜字段。
有一次,我返回了个 timestamp 字段,前端以为是毫秒,其实是秒,结果用户看到的时间全是1970年……从此我们约定:所有时间戳必须是ISO 8601字符串,比如 "2024-06-15T10:30:00Z"。
最后:别怕,你已经在路上了
写这篇文章的时候,窗外天刚亮——没错,我又8点开工了。回想起两年前那个被Cannot find module支配的夜晚,现在的我已经能淡定地 review 别人的PR,还能在技术方案会上和架构师battle几轮。
Node.js没那么可怕。它只是一个让你用熟悉的JavaScript,去构建高性能服务端应用的工具。关键不是语法,而是工程思维:如何组织代码、如何处理错误、如何保证稳定、如何协作开发。
如果你刚入门,别急着造轮子。先模仿、再理解、最后创新。多看GitHub上的优质开源项目(比如Fastify、NestJS),你会发现,高手写的代码,读起来像散文。
对了,上周那个日志服务重构,现在已经稳定运行半年,QPS峰值5000+,内存占用不到200MB。运维小哥终于不用半夜打电话骂我了——这大概就是程序员最大的成就感吧。
加油,少年。你的第一个Hello World服务器,值得被世界看见。

评论 0