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

写码的老王
2025-12-14 19:49
阅读 225

大家好,我是小张,一个刚毕业的大专计算机应届生。去年秋招的时候,靠着自己在家自学前端(主要是 Vue + TypeScript)混进了一家做 Web3 工具链的创业公司,现在远程办公中——每天早上8点准时坐到电脑前,咖啡还没泡好,VSCode 就已经开了三个窗口了。

说实话,我一开始只想着写写页面、调调样式,做个“切图仔”也挺香。但入职不到两周,CTO 直接甩给我一个任务:“你不是会 JS 吗?后端接口也顺手搞一下吧。” 当时我内心OS:我特么只会 console.log 啊!但转念一想,这不就是涨薪跳槽的绝佳机会吗?于是咬咬牙,硬着头皮啃起了 Node.js

今天这篇文章,就是想给和我一样从零起步的兄弟们,分享一段真实的“血泪史”——如何用 Node.js 写出第一个能跑在生产环境的服务端程序。我会结合我们团队最近上线的一个区块链项目运营后台的真实场景,聊聊踩过的坑、熬过的夜,以及为什么 GitHub 上那些 star 很高的模板代码其实根本不能直接抄。


起因:产品经理要一个“实时监控钱包余额”的运营面板

事情是这样的。我们公司主要做 NFT 发行工具,最近老板为了“精细化运营”,要求产品加一个功能:运营同学能在后台实时看到用户钱包里有多少代币,然后针对性推送空投活动

乍一听很简单——前端轮询接口不就完了?但测试同学一句话把我问懵了:“如果同时有 1000 个运营开着页面,每秒请求一次,你的服务器扛得住吗?” 我当场冷汗直流。

更麻烦的是,这个数据要对接 以太坊节点,每次查询都要走 JSON-RPC,延迟高不说,还容易被限流。运维大哥(人称“K8s 教父”)冷冷地丢下一句:“别让我的集群因为你写的垃圾服务崩了。”

那一刻,我知道:光会 fetch 是不够的。得上 Node.js + WebSocket + 缓存策略 的组合拳。


第一步:别再用 http.createServer 手搓了!

很多新手教程(包括我最早看的)都喜欢从原生 http 模块开始:

const http = require('http');
http.createServer((req, res) => {
  res.end('Hello World');
}).listen(3000);

看起来很酷,对吧?但现实是——你不会在真实项目里这么干。就像没人会徒手造轮子去上班一样。

我们团队用的是 Express,轻量、生态成熟、中间件丰富。而且它和前端的 Express Router 风格一致,我这种前端转过来的学起来毫无压力。

初始化一个 Express 项目超级简单:

mkdir wallet-monitor-backend
cd wallet-monitor-backend
npm init -y
npm install express

然后写个最简 server:

// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/health', (req, res) => {
  res.json({ status: 'ok', uptime: process.uptime() });
});

app.listen(PORT, () => {
  console.log(`🚀 Server running on port ${PORT}`);
});

别笑!这个 /health 接口可是 K8s 的救命稻草。运维配置的 liveness probe 就靠它判断服务是不是假死。上周五晚上我就因为忘写这个,导致 Pod 被无限重启,差点通宵。


第二步:对接区块链数据 —— 别把密钥写死在代码里!

接下来要调用以太坊节点。我们用的是 ethers.js,比 web3.js 更现代,API 也更清爽。

但问题来了:节点 URL 和 API Key 怎么管理

我第一次提交代码,直接把 Infura 的 key 写在 config.js 里,push 到 GitHub 私有仓库。结果 CI 自动触发安全扫描,Slack 瞬间炸锅:“🚨 SECRET LEAK DETECTED”。

运维老哥直接 @ 我:“你是想让我帮你付 10 万刀的账单吗?”

教训惨痛。后来改用 环境变量 + .env 文件(但不提交到 Git)

# .env
INFURA_PROJECT_ID=your_real_id_here
ETHEREUM_NETWORK=mainnet

代码里用 dotenv 加载:

require('dotenv').config();
const { INFURA_PROJECT_ID, ETHEREUM_NETWORK } = process.env;

const provider = new ethers.InfuraProvider(
  ETHEREUM_NETWORK,
  INFURA_PROJECT_ID
);

记住:.env 必须加到 .gitignore 里!我们团队的 pre-commit hook 还加了脚本,一旦检测到代码里有 infura.io 字样就直接拒绝提交——防呆设计,真香。


第三步:性能优化 —— 别让运营刷新页面搞崩服务器

回到核心问题:如何高效获取钱包余额

最初我傻乎乎地在每个 /balance/:address 请求里都去查链上:

app.get('/balance/:address', async (req, res) => {
  const balance = await provider.getBalance(req.params.address);
  res.json({ balance: ethers.formatEther(balance) });
});

结果压力测试一跑,QPS 刚过 50,Infura 就返回 429 Too Many Requests。运营同学还没看到数据,服务先挂了。

解决方案?缓存 + 异步更新

我们引入了 Redis(运维说:“你要是不用缓存,就滚去用 PHP”),配合 node-cache 做本地内存兜底:

缓存层级 TTL 用途
内存 (node-cache) 30 秒 防止同一地址高频请求
Redis 5 分钟 跨实例共享,持久化

关键代码:

const NodeCache = require('node-cache');
const myCache = new NodeCache({ stdTTL: 30 });

app.get('/balance/:address', async (req, res) => {
  const { address } = req.params;
  
  // 先查内存
  let balance = myCache.get(address);
  if (balance) {
    return res.json({ balance, source: 'cache' });
  }

  // 再查 Redis(伪代码)
  balance = await redis.get(`balance:${address}`);
  if (balance) {
    myCache.set(address, balance); // 回填内存
    return res.json({ balance, source: 'redis' });
  }

  // 最后查链上
  try {
    const rawBalance = await provider.getBalance(address);
    balance = ethers.formatEther(rawBalance);
    
    // 写入两级缓存
    myCache.set(address, balance);
    await redis.setex(`balance:${address}`, 300, balance); // 300秒
    
    res.json({ balance, source: 'blockchain' });
  } catch (err) {
    console.error('Chain query failed:', err);
    res.status(500).json({ error: 'Failed to fetch balance' });
  }
});

上线后,Infura 调用量下降了 98%,运营同学疯狂点赞。产品经理甚至想把这个“技术亮点”写进周报里邀功(笑)。


第四步:实时性?WebSocket 才是王道!

虽然缓存解决了性能问题,但运营还是抱怨:“我刚转了 ETH 进去,页面怎么还不刷新?”

这时候轮询已经 out 了。我们决定上 WebSocket,用 ws 库(轻量,无依赖):

const WebSocket = require('ws');
const wss = new WebSocket.Server({ server }); // 复用 Express server

wss.on('connection', (ws, req) => {
  console.log('New client connected');
  
  ws.on('message', (data) => {
    const msg = JSON.parse(data);
    if (msg.type === 'subscribe') {
      // 订阅某个地址
      subscribeAddress(ws, msg.address);
    }
  });
});

配合定时任务(用 node-cron)每 30 秒拉一次链上最新余额,如果有变化就推送给订阅的客户端:

const cron = require('node-cron');

cron.schedule('*/30 * * * * *', async () => {
  for (const address of subscribedAddresses) {
    const newBalance = await getBalanceFromCacheOrChain(address);
    if (newBalance !== lastKnownBalance[address]) {
      // 广播给所有订阅该地址的客户端
      broadcastToSubscribers(address, newBalance);
      lastKnownBalance[address] = newBalance;
    }
  }
});

前端用 useEffect 建立连接,体验丝滑如德芙。运营同学终于不用狂按 F5 了——虽然他们现在的问题变成了:“能不能自动弹窗提醒我?”(产品经理,你听见了吗?)


GitHub 不是终点,而是起点

很多人以为把代码 push 到 GitHub 就完事了。但在我们团队,CI/CD 流程才是真正的试金石

我们的 GitHub Actions 配置如下:

# .github/workflows/ci.yml
name: CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test
      - run: npm run lint

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to K8s
        run: |
          echo "${{ secrets.KUBE_CONFIG }}" > kubeconfig
          kubectl --kubeconfig=kubeconfig set image deployment/wallet-api \
            wallet-api=ghcr.io/our-org/wallet-api:${{ github.sha }}

每次 merge 到 main,自动构建 Docker 镜像并滚动更新 K8s Deployment。运维说:“只要你的镜像能跑起来,我就给你加星。”(当然,他加的是 Slack 表情 😅)


给新手的几条真心话

  1. 别怕犯错:我第一次部署就把数据库密码写成了明文,被全组围观。但正是这些“社死”时刻让我成长最快。
  2. 善用社区:GitHub 上搜 awesome-nodejs,一堆高质量资源。但别盲目 copy,先看 issue 区有没有坑。
  3. 关注安全:永远不要信任前端传来的任何数据。body-parser 解析后记得校验,推荐用 zod
  4. 日志很重要:用 winstonpino 结构化日志,K8s 里查起来快如闪电。
  5. 你不是一个人在战斗:我们团队每周五下午有“Bug Bash”环节,一起 review 代码、吐槽需求,氛围超好。

写在最后

从那个连 process.env 都不知道是什么的小白,到现在能独立负责一个微服务模块,Node.js 给了我从前端走向全栈的底气。虽然工资还没翻倍(哭),但至少——我不再害怕后端这个词了。

如果你也是大专出身、自学成才,正在焦虑自己的职业路径,我想说:技术栈没有高低贵贱,解决问题的能力才是硬通货。管你是用 Express 还是 NestJS,只要能让运营少骂一句“这破系统又卡了”,你就是团队 MVP。

好了,咖啡喝完了,该去修下一个 Bug 了。如果你觉得这篇文章有点用,欢迎去 GitHub 给我点个 ⭐(我的主页在 bio 里)。咱们下次聊 如何用 Node.js 写一个能抗住双11流量的限流中间件 —— 毕竟,老板已经暗示今年大促要上了 😅

Happy Coding!

评论 0

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