Node.js新手教程:从零开始学习服务器端JavaScript
大家好,我是小陈,某985计算机专业大三狗,秋招季快到了,最近一边刷LeetCode一边肝项目。在我们实验室的后端组已经混了快两年,日常开发基本靠Vim + tmux + 终端三件套,IDE?那是什么?能吃吗?
说起来有点惭愧,虽然搞了这么久后端,但之前一直用的是Springboot(别问为什么,问就是导师和师兄们都是Java系的)。直到上个月,隔壁前端组的老王甩给我一个需求:“小陈,这个新项目要搞个轻量级API服务,用Node.js吧,快!双11前必须上线!”我当场就懵了——Node.js?不就是那个传说中“回调地狱”、“内存泄漏”的玩意儿?
但没办法,deadline就在眼前,产品经理已经在群里@了三次,再不接锅就要被拉去站会“友好交流”了。于是,我含泪打开了Node.js官网,开始了我的JS后端之旅。
为啥是Node.js?不是Springboot?
先说清楚,我不是要踩Springboot。事实上,在我们组,大部分核心系统还是用Springboot写的,稳如老狗。但这次的需求不一样:需要快速迭代、轻量部署、对接React前端,而且数据逻辑不算复杂。
产品经理原话是:“我们这个只是个中间层,把几个第三方API聚合一下,返回给前端就行。”
我一听:这不就是典型的BFF(Backend For Frontend)场景嘛!
对比了一下:
| 技术栈 | 启动速度 | 内存占用 | 开发效率 | 团队熟悉度 |
|---|---|---|---|---|
| Springboot | 慢(30s+) | 高(500MB+) | 中 | 高 |
| Node.js | 快(1s内) | 低(50MB左右) | 高 | 低(对我而言) |
再加上前端同事用的是React,他们天天喊着“全栈JavaScript”,我就想:不如趁机学一波,还能和前端对齐技术栈,以后联调不用再互相甩锅(bushi)。
环境搭建:别被nvm劝退
第一步当然是装Node。但千万别直接下安装包!我第一次就是这么干的,结果版本乱成一锅粥,npm global装的包到处找不到。
正确姿势:用 nvm(Node Version Manager)
# 安装 nvm(Mac/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# 安装 LTS 版本(推荐新手用)
nvm install --lts
nvm use --lts
# 验证
node -v # v20.12.0(举例)
npm -v # 10.5.0
💡 小贴士:Windows用户可以用
nvm-windows,但建议直接上WSL2,体验飞起。
装完之后,我新建了个目录 bff-service,然后:
npm init -y
生成 package.json,一切看起来都很美好……直到我写了第一行代码。
Hello World?不,是Hello Callback Hell
我照着官方文档写了个最简单的HTTP服务器:
// server.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from Node.js!');
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
跑起来没问题。但当我尝试读个文件、查个数据库、再调个外部API时,代码迅速变成了这样:
fs.readFile('config.json', (err, data) => {
if (err) throw err;
const config = JSON.parse(data);
db.connect(config.db, (err, conn) => {
if (err) throw err;
conn.query('SELECT * FROM users', (err, rows) => {
if (err) throw err;
// ... 还有三层嵌套
});
});
});
我当时看着屏幕,手都抖了——这不就是传说中的“回调地狱”吗?难怪前辈们谈Node色变。
但!时代变了!
现在的Node.js早就支持 async/await 了,配合 Promise,代码可读性直线上升:
const fs = require('fs').promises;
async function getUserData() {
try {
const data = await fs.readFile('config.json', 'utf8');
const config = JSON.parse(data);
const conn = await db.connect(config.db);
const users = await conn.query('SELECT * FROM users');
return users;
} catch (err) {
console.error('Failed to fetch user data:', err);
throw err; // 别吞异常!运维半夜会找你喝茶
}
}
看到没?清晰、线性、可维护。作为一个注重代码可读性的人,我当场就爱上了这种写法。
Express:别造轮子,站在巨人肩膀上
虽然原生 http 模块很轻量,但真要写个正经服务,还是得用框架。Express 是最经典的选择,轻量、灵活、生态丰富。
npm install express
改造一下之前的服务器:
// app.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
// 中间件:解析JSON请求体
app.use(express.json());
// 路由
app.get('/api/users', async (req, res) => {
try {
const users = await getUserData(); // 上面那个函数
res.json({ success: true, data: users });
} catch (err) {
res.status(500).json({ success: false, message: 'Internal Server Error' });
}
});
app.listen(PORT, () => {
console.log(`Express server running on port ${PORT}`);
});
是不是清爽多了?而且Express的中间件机制特别适合做日志、鉴权、错误处理等横切关注点。
🚨 血泪教训:千万别在生产环境直接返回
err.message!曾经有一次我把数据库密码拼错,结果错误信息直接返回给了前端,差点被安全团队抓去“喝茶”。
和React前后端联调:跨域问题怎么破?
我们的前端是React + Vite,本地开发跑在 localhost:5173,而我的Node服务在 localhost:3000。一调接口,浏览器直接报:
Access to fetch at 'http://localhost:3000/api/users' from origin 'http://localhost:5173' has been blocked by CORS policy.
啊,经典的CORS(跨域资源共享)问题。
解决方法很简单,加个中间件:
npm install cors
const cors = require('cors');
app.use(cors()); // 允许所有来源(仅开发环境!)
但注意:生产环境千万别这么干! 正确做法是指定允许的域名:
app.use(cors({
origin: ['https://your-frontend.com', 'https://staging.your-frontend.com']
}));
前端同事看到接口通了,立马在群里发了个“👍”,我心里也松了口气——终于不用再听他念叨“后端又搞什么鬼”了。
工具链:提升开发幸福感的关键
作为一个Vim党,我对工具的要求很高。光写代码不够,还得高效、可调试、可监控。
1. 自动重启:nodemon
改一行代码就要手动重启?No way!
npm install -g nodemon
nodemon app.js
保存即重启,开发体验丝滑。
2. 调试:VS Code + Chrome DevTools
虽然我不常用IDE,但调试时还是会打开VS Code。在 .vscode/launch.json 里配好,就能打断点、看变量,比 console.log 香多了。
3. 日志:winston
别再用 console.log 打日志了!用 winston 分级别、输出到文件、甚至接入ELK:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
上周五晚上加班排查一个偶发Bug,就是因为日志太粗糙,最后靠 winston 的详细上下文才定位到是某个第三方API偶尔超时导致的。
从Springboot切换过来的几点体会
- 启动速度:Springboot冷启动30秒 vs Node.js 1秒,开发效率天差地别。
- 内存占用:Node.js单进程通常50-100MB,Java动辄500MB+,容器化部署更省资源。
- 异步模型:Node.js天生事件驱动,适合I/O密集型任务(比如API聚合),但CPU密集型任务要小心,别阻塞事件循环。
- 类型安全:这点Java完胜。不过现在可以用TypeScript补救(我们新项目已经全面TS化了)。
写在最后:代码人生,不止一种可能
说实话,刚开始接触Node.js时,我是带着偏见的。总觉得“脚本语言搞后端不靠谱”。但真正用起来才发现,只要架构合理、代码规范、监控到位,Node.js完全可以支撑高可用服务。
而且,作为学生,掌握多一种技术栈,秋招时就多一分底气。上周刚面完一家大厂,面试官听说我会Node.js + React + Springboot,眼睛都亮了:“你们学校教这么多?” 我只能笑笑:“自学的,被需求逼的。”
技术没有高低贵贱,只有适不适合。Springboot适合复杂业务系统,Node.js适合快速迭代和I/O密集场景。作为一个即将踏入职场的开发者,我越来越觉得:真正的“代码人生”,是能根据问题选择最合适的工具,而不是被工具限制了思维。
所以,如果你还在犹豫要不要学Node.js,我的建议是:Just try it. 从一个简单的API开始,慢慢构建你的服务。遇到坑?正常,我踩过的比你多。但每解决一个Bug,你就离“靠谱后端工程师”更近一步。
共勉。
P.S. 本文所有代码已整理到GitHub,欢迎Star(求简历加分 😅):github.com/chensan/bff-node-demo
P.P.S. 产品经理刚刚又在群里@我:“小陈,下个需求能不能用Go写?” …… 我先去哭一会儿。

评论 0