一、Node.js 到底是什么
上一篇我们写了一段简短的代码,用 Node.js 启动了一个 HTTP 服务,浏览器访问 http://localhost:3000 就能看到一段文字。
但你可能会有一个疑问:JavaScript 不是浏览器里跑的吗?为什么在终端里敲 node app.js 也能跑 JavaScript?
要回答这个问题,需要理解一个概念:运行时。
编程语言本身只是一套语法规范,需要一个程序来真正“执行”它。浏览器就是一个 JavaScript 的运行时——它内置了 JS 引擎(Chrome 用的是 V8 引擎),负责解析和执行 JavaScript 代码。同时,浏览器还提供了很多 JS 可以调用的能力,比如操作 DOM、发送网络请求、处理定时器等。
Node.js 是另一个运行时。它把 Chrome 的 V8 引擎单独拿出来,加上了一套服务器端需要的 API——比如读写文件、创建 HTTP 服务、操作数据库。所以,JavaScript 代码在 Node.js 里能做的事情和浏览器里完全不同:
- 浏览器里的 JS:操作页面 DOM、监听点击事件、
alert弹窗、localStorage。 - Node.js 里的 JS:读写文件、创建 HTTP 服务、连接数据库、执行系统命令。
同一个 JavaScript 语言,两个不同的运行环境,两套不同的能力。
这也是为什么你用 JS 写前端已经会了语法、变量、函数、对象,现在学后端只需要学“在 Node.js 环境下能做什么新的事情”,而不需要重新学一门语言。
二、安装 Node.js
如果你还没装,现在装。
第一步:下载
打开浏览器,访问 https://nodejs.org。你会看到两个版本:
- LTS(Long Term Support,长期支持版):稳定可靠,适合学习和生产。推荐装这个。
- Current(最新版):功能最新,但可能有未发现的 bug。新手不用选这个。
点击左边的 LTS 按钮,下载适合你操作系统的安装包。
第二步:安装
双击下载好的安装包,一路点“下一步”,全部用默认选项即可。Mac 用户拖动 Node.js 图标到 Applications 文件夹。
第三步:验证安装是否成功
安装完成后,打开终端:
- Windows:按键盘上的
Win + R,输入cmd,回车。黑色窗口就是终端。 - Mac:在“启动台”里找到“终端”应用,或者按
Cmd + 空格搜索“终端”。
在终端里输入以下命令并回车:
node -v
如果出现类似 v20.10.0 或 v18.17.0 这样的版本号,说明 Node.js 安装成功。
再输入:
npm -v
npm 是 Node.js 自带的包管理工具,用来安装第三方的代码库(就像手机上的应用商店)。如果出现版本号,说明 npm 也正常。
三、写出你的第一个 Node.js 程序
现在我们来正式写第一段后端代码,并逐行拆解。
第 1 步:新建文件
在电脑上任意地方新建一个文件夹,比如桌面上新建一个叫 backend-learn 的文件夹。
用 VS Code 打开这个文件夹(可以直接把文件夹拖到 VS Code 图标上)。
在 VS Code 左侧文件列表里,右键 → 新建文件,命名为 app.js。
第 2 步:写代码
在 app.js 里输入以下代码:
const http = require('http');
const server = http.createServer(function (request, response) {
response.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('你好,这是你的第一个后端程序!');
});
server.listen(3000, function () {
console.log('服务已启动,在浏览器中访问 http://localhost:3000');
});
第 3 步:运行
在 VS Code 里按 Ctrl + `(注意是键盘左上角数字 1 左边的那个键),打开 VS Code 内置的终端。
在终端里输入:
node app.js
终端会显示:
服务已启动,在浏览器中访问 http://localhost:3000
这时候不要关闭终端。终端里的这个程序正在持续运行,等待浏览器的请求。
第 4 步:访问
打开浏览器,在地址栏输入 http://localhost:3000,回车。
你会看到页面显示:你好,这是你的第一个后端程序!
这就是你的第一个后端程序。虽然只有 7 行代码,但它是一个货真价实的 HTTP 服务器。
四、逐行拆解这段代码
现在一行一行来。这是本篇最核心的部分,仔细看。
第 1 行:const http = require('http');
require是 Node.js 用来加载模块的函数。模块就是别人写好的、封装了特定功能的代码包。'http'是 Node.js 内置的模块,专门用来创建 HTTP 服务和处理 HTTP 请求。因为它是内置的,所以不需要额外安装,直接require就能用。const http把加载进来的 http 模块赋值给一个常量http。之后要使用 http 模块的功能,就通过这个http变量来调用。- 整行翻译:“帮我把 Node.js 内置的 http 模块加载进来,存到
http这个变量里,后面要用。”
第 2 行:空行
这是一行空行,纯粹为了让代码看起来不拥挤。没有实际功能。
第 3 行:const server = http.createServer(function (request, response) {
这行比较长,拆开来看:
http.createServer()是 http 模块提供的一个方法,用来创建一个 HTTP 服务对象。调用之后,你就有了一个服务对象,可以监听端口、接收请求、返回响应。function (request, response) { ... }是一个回调函数。这个函数会在每次有请求进来的时候被自动调用。不请求就不调用,来一次请求就调用一次。request:这个参数包含了请求的所有信息——谁发的、请求的路径是什么、带了什么参数、用了什么方法。response:这个参数是一个对象,用来构建你要返回给客户端的响应——设置状态码、设置响应头、写响应体。const server:把创建好的服务对象存到server变量里,后面要告诉它监听哪个端口。- 整行翻译:“创建一个 HTTP 服务。每次有人发请求过来,就执行这个回调函数,把请求信息放在
request里,把用来构建响应的工具放在response里。服务对象存到server变量里。”
第 4 行:response.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
response.writeHead()用来设置响应头。响应头就是告诉浏览器“我返回给你的东西是什么类型、有多少字节、状态怎么样”等描述性信息。200:HTTP 状态码。200 的意思是“成功,一切正常”。浏览器看到 200 就知道“请求被正常处理了,返回的数据可以用”。'Content-Type':告诉浏览器“我返回给你的内容是什么类型”。这里设为'text/plain; charset=utf-8',意思是“纯文本,字符编码是 UTF-8(支持中文)”。- 为什么要有 Content-Type:如果不告诉浏览器内容类型,浏览器可能把 HTML 代码当纯文本显示,或者把图片数据当乱码显示。我们这里返回的是纯文本,所以设
text/plain。如果返回的是网页(HTML),应该设text/html。如果返回的是 JSON 数据,应该设application/json。 - 整行翻译:“把响应状态设为 200(成功),内容类型设为纯文本(支持中文)。”
第 5 行:response.end('你好,这是你的第一个后端程序!');
response.end()做了两件事:第一,把括号里的内容(这里是那段中文字符串)写入响应体,作为返回给浏览器的实际内容;第二,结束本次响应,告诉服务器“我写完了,你可以发送给客户端了”。- 注意:
end()必须调用,否则浏览器会一直等,直到超时。每个请求必须对应一次end()。 - 整行翻译:“把我给你的这段文字返回给浏览器,然后结束本次响应。”
第 6 行:});
这是第 3 行 http.createServer(function (request, response) { 的闭合花括号和括号。表示回调函数定义结束,createServer 方法调用结束。
第 7 行:空行
同样是为了让代码看起来有呼吸感。
第 8 行:server.listen(3000, function () {
server.listen()是服务对象的一个方法,用来让服务开始监听某个端口,等待请求进来。3000:端口号。一台服务器上可以同时运行很多个服务——Web 服务、邮件服务、数据库服务——端口号就是用来区分它们的。你可以把端口想象成快递柜的格子编号:快递员(客户端)送到某个格子号(端口号),对应的服务就在那个格子里取件。常见默认端口:HTTP 网站通常用 80 端口,HTTPS 用 443,MySQL 数据库用 3306。这里我们用 3000,因为它没有被其他软件占用,且是 Node.js 教程的惯例。function () { ... }是一个回调函数,在服务成功启动并开始监听后执行一次。用于通知你“服务已经准备好了”。- 整行翻译:“服务准备好了之后,在 3000 端口上等待请求,启动成功后执行回调函数通知我。”
第 9 行:console.log('服务已启动,在浏览器中访问 http://localhost:3000');
console.log()在 Node.js 里的用法和浏览器里一样——在终端打印一行文字。localhost:这是你电脑自己的网络名。无论你有没有联网,localhost都指向你正在使用的这台电脑。访问localhost就等于访问你自己的电脑。当你开发一个后端程序时,你就是用自己的电脑模拟服务器,所以通过localhost来测试。3000:端口号,和上面listen里设置的一致。- 整行翻译:“在终端打印一行提示,告诉开发者服务已经启动,可以去浏览器访问了。”
第 10 行:});
闭合第 8 行的函数体和 listen 调用。
五、你可能会遇到的两个问题
问题一:终端里光标一直在闪,什么也没发生?
这是正常的。当你执行 node app.js 后,Node.js 就开始持续运行了。它在等请求进来,不会自动退出。光标闪烁就是程序在运行的表现。只要不关终端,服务就一直活着。
要停止服务,在终端里按 Ctrl + C(Mac 也是 Control + C)。这会终止正在运行的程序,终端光标回到正常的可输入状态。
每当你修改了 app.js 的代码,都需要先 Ctrl + C 停止服务,再重新执行 node app.js,修改的代码才会生效。
问题二:端口被占用了?
如果你忘了停止上一次启动的服务,又开了一个新终端执行 node app.js,可能会看到类似 Error: listen EADDRINUSE :::3000 的错误。意思是 3000 端口已经被占用了。
解决:找到上一次运行的终端窗口,按 Ctrl + C 停掉它。或者换一个端口号,比如把代码里的 3000 改成 3001,访问的时候也改成 http://localhost:3001。
六、改造你的第一个程序
光是看一遍不够。现在请打开你的 app.js,做以下三次改造,每改一次就重启服务、刷新浏览器观察效果。这才是真正掌握的过程。
改造一:把返回的文字换成你自己的
response.end('欢迎来到我的后端世界!');
改造二:把端口号从 3000 改成 8080
server.listen(8080, function () {
console.log('服务已启动,在浏览器中访问 http://localhost:8080');
});
注意:listen 里的端口号和 console.log 里的端口号必须一致。
改造三:返回一段 HTML 而不是纯文本
把 Content-Type 改成 text/html,end 里的内容写成 HTML 标签:
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
response.end('<h1>你好</h1><p>这是一段 HTML 内容。</p>');
刷新浏览器,你会看到标题和段落被浏览器渲染出来了,而不是显示原始代码。因为你告诉浏览器“我返回的是 HTML”,浏览器就把这些标签解析成可视化的页面元素。
七、本篇小结
这一篇你学会了:
- Node.js 是什么:一个把 Chrome V8 引擎拿出来、加上服务器端 API 的 JavaScript 运行时。
- 运行时是什么:执行代码的程序环境。浏览器是一个运行时,Node.js 是另一个。
require是什么:加载模块的函数。http是 Node.js 内置模块,专门处理 HTTP。createServer的作用:创建一个 HTTP 服务对象,每次请求进来会执行回调函数。request和response:前者包含请求信息,后者用来构建并发送响应。writeHead:设置响应状态码和头部信息(如内容类型)。end:写入响应体内容并结束响应。必须调用。listen:让服务在指定端口上开始监听,等待请求。localhost和端口号:localhost就是你自己电脑,端口号用来区分同一台电脑上不同的服务。Content-Type:告诉浏览器返回的是什么类型的内容,text/plain是纯文本,text/html是网页。
你现在已经能写一个可以返回文字、返回 HTML 的 HTTP 服务了。虽然简陋,但它包含了后端程序最核心的骨架。下一篇,我们会学模块系统——怎么把自己的代码拆成多个文件,以及 Node.js 内置的其他常用模块。
下一篇预告
下一篇——《模块系统——拆开你的代码》:require 的完整用法、module.exports 怎么导出自己的函数、内置模块 fs 怎么读写文件、path 怎么处理文件路径。你会写出第一个可以读取本地文件并返回内容的接口。
后端零基础入门,每周更新。













暂无评论内容