尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Node.js 实战入门:从环境安装到命令行工具开发

Node.js 实战入门:从环境安装到命令行工具开发 如果你刚入行前端一两年或者准备往后端方向走应该没少听过 Node.js 的名字但很可能一直没搞明白它到底能干什么。官方定义说它是“基于 V8 引擎的 JavaScript 运行时”这句话挺劝退的。翻译成人话就是以前 JavaScript 只能在浏览器里跑装上 Node.js 之后你在电脑上也能直接跑 JS 了能读写文件、能开 HTTP 服务、能连数据库、能写自动化脚本甚至能干很多传统脚本语言干的活。如果你会 JavaScript学它的门槛其实非常低这也是它能在短时间内火遍全栈领域的原因。这篇教程不打算带你抄一个 Express 博客就完事我会按自己这些年实际用下来的经验从版本选型、环境安装讲起再带你把 http、fs、path 这些核心模块玩明白最后手写一个能用的命令行工具。文章面向从零基础到初中级的开发者如果你已经会写点 JS跟着敲一遍基本就能形成一张完整的地图如果你完全没写过代码我建议先把 JS 基础语法花一天补一下再回来体验会好很多。1. 思路拆解学 Node.js 的正确路径与版本选型1.1 学习路线怎么定先从核心模块入手别急着上框架我见过太多人学 Node.js上来就照着视频搭一个 Express 服务器折腾一晚上把环境配好npm install 敲了好几遍最后除了记住“装依赖要按回车”之外啥也没留下。这种学习方式最大的问题是你把框架跑起来了但完全不知道它背后在做什么遇到问题只能靠猜。我的建议路线很明确四个阶段理解运行时机制事件循环、非阻塞 I/O、回调与异步这些概念是 Node.js 的根。玩熟内置核心模块http、fs、path、os、url这些是 Node.js 自带的能力不需要装任何第三方包。写一个零依赖的小工具比如批量重命名文件、读取配置文件、起一个静态服务器。再碰 npm 和框架Express、Koa、或者直接上 NestJS这时候你会觉得这些框架其实都是纸老虎。这条路线背后的逻辑很简单。Node.js 生态里 90% 的第三方库都是在核心模块之上做封装。你把核心模块用过一遍再看 Express 源码就只是在看函数调用和语法糖心里不会发慌。更重要的是原生模块写代码时你会被迫面对 Node.js 最核心的异步风格和错误处理习惯这种“肌肉记忆”是任何框架都教不会的。我见过一些同学直接用 Koa 入门写路由写得飞起但问他req和res到底是什么、请求生命周期是怎么样的完全答不上来。这种基础不牢后面排查线上问题会非常痛苦。1.2 版本选型LTS 还是 Currentnvm 为什么是首选打开 Node.js 官网下载页你会发现有两个大按钮左边是 LTS右边是 Current。很多新手在这个页面就蒙了我给你的答案很直接除非你有明确的新特性需求否则一律选 LTS。LTS 全称 Long Term Support翻译叫“长周期支持版本”。它进入维护期之后官方会持续提供安全修复和稳定性补丁周期长达 30 个月期间不会频繁引入破坏性 API 变更。生产环境用 LTS 是最省心的你不会因为升级一个小版本就出现莫名其妙的兼容问题。Current 版本相当于“尝鲜版”适合想体验新语法、或者你维护的库需要提前适配新 V8 特性的人。日常开发老老实实待在 LTS 轨道上。还有一个常见坑特别是在 Ubuntu 上直接用apt install nodejs装出来的版本往往特别老可能还是 12.x 或者 14.x。因为系统源里的包不会跟着上游频繁更新。而现在的构建工具Vite、Tailwind 这些经常要求 Node.js 18 甚至 20 起步系统源根本满足不了。所以我的标准做法是不用 apt改用 nvm 管理 Node 版本。nvmNode Version Manager是 Linux/macOS 上最流行的 Node 版本管理器几个好处相当实际不用 sudo直接装在用户目录绕开了系统权限的坑。可以随时在多个 Node 版本之间切换项目要求什么版本就切什么版本。全局安装的包也落在用户目录不需要管理员权限后面加包不加包都舒坦。Windows 用户可能没法直接用 nvmWindows 下的 nvm 并非官方维护更推荐用 fnm 或者直接装官方 msi 安装包。但 Linux 和 macOS 上我强烈建议统一用 nvm。团队协作时成员 Node 版本不一致经常会出现“我本地能跑你那边报错”的诡异情况用 nvm 能把这个变量彻底干掉。2. 环境准备Ubuntu 上安装 Node.js 20 的完整记录2.1 用 nvm 安装五步搞定我在 Ubuntu 服务器上部署项目时装 Node 环境已经循环过几十次了流程早就固定下来。这里以 Ubuntu 20.04 / 22.04 为例给你一套可以直接抄的步骤。第一步安装 nvm。官方推荐的安装方式是执行一个远程脚本踩过的坑多了之后我的建议是先确认脚本来源是 nvm 官方仓库再执行。curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash这里的v0.39.7是我编写时比较稳定的版本你可以去 nvm 的 GitHub releases 页面看最新的 tag替换掉即可。第二步重新加载 shell 配置。安装脚本执行完之后nvm 会把几行环境变量写入你的 shell 配置文件。如果你用的是 bash执行source ~/.bashrc如果你是 zsh 用户要记得改成source ~/.zshrc。很多新手卡在这一步装完了直接敲nvm提示 command not found其实就是没重新加载配置。我建议干脆重开一个终端窗口。第三步验证 nvm 是否装好nvm --version如果输出版本号说明 nvm 就位了。第四步安装 Node.js 20。注意这里有个细节我写的是nvm install 20不是nvm install 20.17.0。nvm install 20为什么只写主版本号因为 nvm 会自动解析这个主版本号下当前最新的 20.x 版本自动拉下来装好。你不需要自己去记最新补丁版本号也不会因为版本号写错遇到下面那种“not yet released”的报错。第五步把 20 设为默认版本。这样以后每次打开新终端自动使用 Node 20nvm alias default 20然后检查安装结果node -v npm -v两个命令都有版本输出就说明环境完全可用了。2.2 Windows 和 macOS 的安装差异虽然上面主要讲了 Ubuntu但我知道大部分读者本地开发用的是 macOS 或者 Windows这里简单补充一下。macOS 上如果不想用 nvm可以用 Homebrewbrew install node20但我的建议还是先装 nvm 再nvm install 20因为用 brew 装的 node 有时候会和系统自带的版本冲突切版本也不方便。Windows 用户我前面提过直接去官网下载 LTS 版本的 msi 安装包是最省事的。装的时候注意勾选“Add to PATH”这个选项默认是选中的别手滑取消。装完后打开 PowerShell 或者 CMD敲node -v验证。另外Windows 用户如果遇到权限问题报 EACCES强烈建议卸载后用 nvm-windows 或者 fnm 管理别跟系统目录死磕。2.3 验证安装和 npm 换源这一步能救命Node 装好之后第一件事不是写代码而是检查 npm 的源。npm 默认源是官方源部署在国内服务器上下载依赖时那速度能让人崩溃装个大点的项目有时候十几分钟还没结束。解决办法很简单把 npm 源换成国内的镜像源。我用的是 npmmirror淘宝镜像的官方继承者npm config set registry https://registry.npmmirror.com验证是否生效npm config get registry这里想说明一下原理镜像源本质上就是把 npm 官方仓库的内容同步到国内的服务器你下载时从离你近的服务器拉取网络延迟大幅降低。代码逻辑完全不变也不影响任何包的功能。但要注意镜像源比官方源可能会延迟同步新版本如果你需要第一时间发布的最新包可以临时指定官方源比如npm install xxx --registryhttps://registry.npmjs.org/。这个步骤看似简单实际价值极高。很多新手项目卡在安装依赖这一步以为是自己网络问题没想到只是没配源。3. 核心 API 实操把 http、fs、path 用起来3.1 HTTP 服务器与路由分发第一次跑起来环境准备完毕我们写第一个有实际意义的程序HTTP 服务器。这是 Node.js 最经典的场景也是你理解服务端工作的最佳入口。新建一个项目目录创建一个server.jsconst http require(http); const server http.createServer((req, res) { const { url, method } req; if (url / method GET) { res.writeHead(200, { Content-Type: text/html; charsetutf-8 }); res.end(h1首页/h1pNode.js 服务器跑通了/p); return; } if (url /api/users method GET) { res.writeHead(200, { Content-Type: application/json; charsetutf-8 }); res.end(JSON.stringify([{ name: 张三 }, { name: 李四 }])); return; } res.writeHead(404, { Content-Type: text/plain; charsetutf-8 }); res.end(404 Not Found); }); server.listen(3000, () { console.log(服务器已启动: http://localhost:3000); });运行方式很简单node server.js然后浏览器访问http://localhost:3000你就能看到自己启动的第一个 Web 服务。这段代码看起来简单但信息量不小。http.createServer接收一个回调函数这个回调会在每次收到请求时被调用。req是请求对象包含 url、method、headers 这些属性res是响应对象用来写回状态码、响应头和响应体。所谓“路由分发”本质上就是在回调里判断 url 和 method然后执行不同分支。这比很多框架里的路由配置直观得多也更容易理解 HTTP 协议的基本交互。这里一定要强调一个细节响应头里的charsetutf-8别省略。我第一次写 Node 接口返回中文浏览器里全是乱码查了半天才发现是没告诉浏览器用 UTF-8 解码。HTTP 默认的编码是 ISO-8859-1也就是 Latin-1中文自然全变问号。加上charsetutf-8之后问题立刻消失。这个坑在新手里出现频率极高索性现在记下来。3.2 文件读写与异步思维同步和异步别混用服务端编程的第二项基本功就是文件读写。Node.js 的fs模块给你提供了两种风格传统的回调风格以及 Promise 风格。我在实际开发中更倾向于 Promise 风格代码更清晰也不容易陷入回调地狱。先看一个读取 JSON 配置文件的例子const fs require(fs/promises); const path require(path); async function readConfig() { const filePath path.join(__dirname, config.json); try { const data await fs.readFile(filePath, utf8); return JSON.parse(data); } catch (err) { console.error(读取配置失败:, err.message); return null; } } readConfig().then((config) { if (config) { console.log(数据库地址:, config.dbHost); } });这里有两个关键点。第一个为什么用fs/promises而不是fs因为fs/promises的 API 返回 Promise可以直接用async/await写顺序感和同步代码几乎没有区别。而传统的fs.readFile是回调风格写嵌套逻辑时层级会越来越深维护起来很头疼。第二个为什么字符串拼接路径不行很多人图省事直接写__dirname /config.json在 Linux 上没问题但换到 Windows 上路径分隔符是反斜杠直接拼接就会出现诡异错误。path.join会帮你根据当前操作系统自动选择正确的分隔符。这是个贴近工程实践的重要细节跨平台项目的路径处理必须全部走 path 模块。顺便解释一下__dirname是什么它是当前模块所在的目录的绝对路径。与之相对的是process.cwd(),它返回的是你执行node命令时所在的目录。这两个值经常不一样初学者非常容易搞混。我的经验是读取模块自己目录下的文件用__dirname处理用户输入的工作目录用process.cwd()。3.3 模块机制CommonJS 与 ESM搞懂 require 和 importNode.js 的模块系统是个绕不开的话题。历史上 Node.js 用的是 CommonJS也就是require(/模块名)这种写法后来 ECMAScript 规范出了正式的模块标准 ESM也就是import xx from xx这种写法。Node.js 从 12 版本开始实验性支持 ESM到 20 版本已经比较成熟了。实际项目里你两种都会遇到老项目基本是 CommonJS新项目尤其是前端工具链相关的大概率是 ESM。所以两个都要看得懂。CommonJS 的核心就两个东西module.exports导出require导入。比如// utils.js function formatTime(date) { return date.toISOString(); } module.exports { formatTime };另一个文件里const { formatTime } require(./utils); console.log(formatTime(new Date()));而 ESM 的写法是export和import// utils.mjs export function formatTime(date) { return date.toISOString(); }// main.mjs import { formatTime } from ./utils.mjs;在使用 ESM 时有个烦人的细节文件后缀名。如果你用import语法文件后缀必须改成.mjs或者在package.json里声明type: module。我给你的建议是新项目直接在 package.json 里写type: module一劳永逸老项目保持 CommonJS 风格不要混用。混用容易踩到requireis not defined 这种莫名其妙的坑。4. 实战演练手写一个批量重命名文件的命令行工具4.1 需求与设计从实际场景出发学了这么多基础 API是时候组合起来做一个能用的东西了。我以自己真实遇到过的场景举例有一次运营同事给我拷了一批素材图文件名全是IMG_001.JPG、IMG_002.JPG这种相机默认命名完全看不出是什么内容。我要求把它们统一改成photo-001.jpg、photo-002.jpg这样的格式并且过滤掉非图片文件。这个需求如果用 Python 写也行但既然我们在学 Node.js正好用它验证一下综合能力。涉及的知识点包括process.argv读取命令行参数、fs/promises读写目录、正则表达式匹配过滤、path.join拼接路径、fs.rename重命名文件。所有知识点前面都出现过现在组合在一起。4.2 完整代码与逐行解释创建一个rename.js#!/usr/bin/env node const fs require(fs/promises); const path require(path); const sourceDir process.argv[2] || ./images; const prefix process.argv[3] || IMG_; async function main() { const files await fs.readdir(sourceDir); const images files.filter((f) /\.(jpg|jpeg|png)$/i.test(f)); if (images.length 0) { console.log(目录下没有找到图片文件); return; } for (const file of images) { const oldPath path.join(sourceDir, file); const seq file.replace(prefix, ).replace(/\.[^.]$/, ); const newName photo-${String(seq).padStart(3, 0)}.jpg; const newPath path.join(sourceDir, newName); await fs.rename(oldPath, newPath); console.log(${file} - ${newName}); } } main().catch((err) { console.error(执行出错:, err.message); process.exit(1); });第一行#!/usr/bin/env node是 shebang它告诉系统这个脚本要用 node 来执行后面我们配置全局命令时靠的就是这行。process.argv[2]是命令行传入的第一个参数process.argv[3]是第二个。比如你执行node rename.js ./images IMG_那sourceDir就是./imagesprefix就是IMG_。用||加上默认值是为了让不传参数时也能跑默认处理当前目录下images文件夹。正则/(\.jpg|\.jpeg|\.png)$/i匹配以这些扩展名结尾的文件i标志表示忽略大小写这样.JPG也能匹配上正好处理相机文件的全大写扩展名。接下来是核心逻辑file.replace(prefix, )把IMG_从文件名里去掉得到001.JPG再.replace(/\.[^.]$/, )去掉最后的扩展名得到001。然后String(seq).padStart(3, 0)把序号补成三位数保证排序正确。如果序号是 1、2、3补零后变成 001、002、003文件排序才正常。这个细节很关键不补零的话 photo-10.jpg 会排在 photo-2.jpg 前面。执行效果如下node rename.js ./images IMG_ IMG_001.JPG - photo-001.jpg IMG_002.JPG - photo-002.jpg IMG_003.JPG - photo-003.jpg这个工具虽然简单但已经具备了一个完整脚本应有的骨架参数处理、错误捕获、过程输出、状态退出码。你可以基于它扩展出压缩图片、加水印、同步到对象存储等功能底子都在了。4.3 加入 npm script 与全局命令脚本写好了总不能每次都敲node rename.js ./images吧。我把它接进 npm 体系。在项目目录下先初始化 package.jsonnpm init -y然后在 package.json 里配置 bin 字段{ name: photo-renamer, version: 1.0.0, bin: { rename-photos: ./rename.js } }接着在项目目录执行npm link这一步会把rename-photos这个命令软链接到全局之后你在任何目录都能直接执行rename-photos ./images这里解释一下npm link的原理它相当于把你当前的包“安装”到了全局但链接到的是你的开发目录所以改代码立即生效不需要重新安装。这是本地调试命令行工具的神器比手动复制文件到/usr/local/bin干净得多。如果你不需要全局命令也可以把脚本挂到 npm script 里在 package.json 的scripts字段加一行scripts: { rename: node rename.js ./images }然后执行npm run rename即可。npm script 的好处是项目成员一看 package.json 就知道有哪些可用命令不用翻 README。5. 常见问题与排查技巧实录5.1 版本安装报错not yet released or is not available这个报错是很多新手的拦路虎完整报错长这样error installing 24.21.0: node.js v24.21.0 is not yet released or is not available说几个真实原因。最常见的是你想装一个特定补丁版本但那个版本号打错了或者这个版本真的还没发布。nvm 安装时会去检查版本号是否存在于远程版本列表中如果不存在就抛出这个错误。解决办法也很简单先看当前有哪些可用版本nvm ls-remote输出列表会很长你可以结合grep过滤比如nvm ls-remote | grep v20。不要写精确补丁号直接nvm install 20让 nvm 自己找该主版本下最新的版本。如果生产环境确实需要锁定版本等官方 release 之后再安装。顺带提一句在写教程或者 snippet 时我习惯只写主版本号比如 18、20、22这样内容过时速度慢一些读者也不容易踩到“版本不存在”的坑。这也是一个写技术内容的小经验。5.2 EACCES 权限问题全局安装包老是报权限错误新手的第二个高频报错是 EACCES典型的场景是你用系统自带的 Node.js比如 apt 装的然后执行npm install -g想全局装个工具结果提示没有写权限。根源在于系统 Node 安装目录属于 root普通用户没有写入权限。很多人的第一反应是加sudo npm install -g xxx我强烈不建议这么干。用 sudo 装全局包之后每次运行也可能遇到权限问题而且有些包会在安装时编译原生模块sudo 后生成的文件属于 root容易把系统搞坏。正确的解法就一个用 nvm。nvm 把 node 装在你自己的用户目录下全局包的安装路径也在这个目录里权限问题直接消失。我已经不止一次把同事从 sudo 泥潭里拉出来了这个建议省心程度极高。5.3 EADDRINUSE 端口被占用服务器起不来写 HTTP 服务器时经常遇到Error: listen EADDRINUSE: address already in use :::3000。意思是 3000 端口已经被其他进程占了。排查和解决lsof -i:3000找到占用端口的进程 PID然后kill -9 PID这里我想分享一个工程上的习惯写脚本启动服务时尽量在代码里处理这个情况。比如监听server.on(error, ...)事件检测到 EADDRINUSE 时自动切换端口号或者至少给出友好提示而不是让用户面对一坨堆栈。线上服务运行时端口被占会导致容器反复重启提前处理能省去很多故障排查时间。5.4 npm install 慢、卡在 idealTree换源加清缓存如果你测试一个项目执行npm install之后卡在idealTree阶段半小时不动第一反应应该是源太慢或者本地缓存出了问题。先执行npm config set registry https://registry.npmmirror.com npm cache clean --force然后删掉项目里的 node_modules 和 package-lock.json重新执行npm install。我在国内服务器上部署项目时这套组合拳基本能解决 90% 的依赖安装问题。还有一个更好的方案直接换用 pnpm 作为包管理器。pnpm 在依赖复用和安装速度上有明显优势而且它同样支持配置镜像源。5.5 常见问题速查表问题常见原因解决办法nvm: command not found没有重新加载 shell 配置执行source ~/.bashrc或重开终端node v24.21.0 is not yet released版本号不存在或未发布用nvm ls-remote查可用版本改用nvm install 20EACCES 权限错误全局安装目录无写权限用 nvm 管理 node不要用 sudo 装包EADDRINUSE 端口占用端口被其他进程占用lsof -i:3000查 PID 后 kill或代码里监听 error 事件npm install 卡住默认源访问慢或缓存损坏换 npmmirror 镜像源清缓存重装接口返回中文乱码响应头没指定 UTF-8res.writeHead(200, {Content-Type: text/html; charsetutf-8})5.6 我的排查心法先看堆栈再搜报错别急着改代码踩了这么多坑之后我想分享一个实用的排查习惯遇到报错先完整读一遍错误堆栈。Node.js 的报错信息设计得已经很友好了会告诉你错误类型、出错文件、行号、调用栈。很多时候答案就在堆栈的前几行。举个例子早期我写异步代码经常遇到Cannot read properties of undefined (reading xxx)。这时候如果直接搜报错得到的答案可能五花八门。但如果先看看是哪个变量是 undefined大概率能猜出来是接口返回的数据结构变了或者异步请求还没返回就访问了数据。定位问题比搜索问题更快。另外一个心法是写日志也要有策略。不要上来就console.log满天飞而是把关键路径上的数据和错误打印出来比如请求参数、接口响应、文件路径。看着日志就能还原现场排查效率翻倍。最后分享几个我常用的调试技巧正文到这里基本把 Node.js 从安装到实战的主线走完了最后补几个我平时用得比较多的小技巧都是能立刻提升效率的。第一个是node --watch。从 Node.js 18 开始原生支持文件变更自动重启不用装 nodemon开发时你改完代码保存服务器自动重启省去了手动停掉再启动的重复操作node --watch server.js第二个是node --inspect。启动调试模式后配合 Chrome 的chrome://inspect页面可以像调试前端代码一样给服务端代码打断点、看变量。对于排查复杂的异步流程这比大量打日志快得多。第三个是process.env.NODE_ENV的使用习惯。写代码时环境变量不要散落各处统一从环境变量里读取并且设置默认值。部署到不同环境时只改环境变量不碰代码省心不止一点。关于 Node.js 的学习我个人的体会是不要试图一次性把所有东西都学完而是带着一个真实的小目标去驱动学习。你可能是想写一个自动化脚本或者是想给自己做个接口服务总之先跑起来一个真实的东西再去看文档效率会高得多。版本号这东西真的不用追新稳定能用比什么都强。
返回列表