Node.js新手教程:从零开始学习服务器端JavaScript

彩虹鱼
2025-12-13 05:38
阅读 417

大家好,我是小陈,某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切换过来的几点体会

  1. 启动速度:Springboot冷启动30秒 vs Node.js 1秒,开发效率天差地别。
  2. 内存占用:Node.js单进程通常50-100MB,Java动辄500MB+,容器化部署更省资源。
  3. 异步模型:Node.js天生事件驱动,适合I/O密集型任务(比如API聚合),但CPU密集型任务要小心,别阻塞事件循环。
  4. 类型安全:这点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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝