Node 基础与简单应用(文件读写、服务器搭建、API、路由配置、NPM介绍)

343 阅读8分钟

1 什么是Node.js

Node.js是一个开源的、基于Chrome V8引擎的JavaScript运行时环境,用于构建高性能、可扩展的网络应用程序。它允许使用JavaScript进行服务器端编程,使得开发人员能够在前端和后端使用同一种编程语言,实现全栈开发。

Nodejs并不是一门编程语言,而是JavaScript在服务器端的运行环境。

Node.js广泛应用于构建各种类型的网络应用程序,包括Web服务器、API服务、实时通信应用、命令行工具等。其灵活性、高性能和丰富的生态系统使得Node.js成为现代Web开发中的重要技术之一。

2 使用命令行与Node交互

  • 进入Node REPL(read-eval-print loop):打开termial,输入node
  • 退出Node REPL的两种方式:.exit或Ctrl^D.
  • 清除命令行:cls
  • 显示所有全局变量:Tab键
  • 下划线变量_(underscore variable):下划线代表上一步的结果

3 node模块(modules)

同样作为JavaScript Runtime,Node.js使JavaScript脱离了浏览器运行,这也意味着Node可以做一些浏览器做不到的事,比如从文件系统中读文件。为了实现这一点,我们需要使用Node模块。

在Node.js中,每个文件都被视为一个模块。模块也被称为包(package),Node.js可以说是围绕着模块这个概念构建的,各种附加功能都储存在一个特定模块中。 例如读取文件这个功能储存在fs模块中。

在项目中常常会引入三种不同的模块,分别是node内置模块、自定义模块、第三方模块,但它们都是通过require函数引入。(第三方模块需要先安装)

引入内置模块fs:

const fs = require('fs');

这行代码会返回一个对象,我们将其储存在变量fs中,这个对象有很多可以用来读写数据的函数。

4 node实现读写文件(同步&异步)

操作文件是服务端的一个基础功能,也是后端开发必备能力之一。操作文件主要包括读和写。而这些功能 NodeJS 都已经提供了对应的方法。

当使用Node读写文件时,可以使用内置模块fs提供的方法。

4.1 同步

使用fs.readFileSyncfs.writeFileSync方法来进行同步的文件读写操作,但这会阻塞执行线程。

//blocking,synchronous way
const fs = require("fs");

//读取文件
const textIn = fs.readFileSync("./txt/input.txt", "utf-8");
console.log(textIn);

//写入文件
const textOut = `This is what we know about avacado: ${textIn}. \nCreate on ${Date.now()}`;
fs.writeFileSync("./txt/output.txt", textOut);
console.log("File written!");

note:readFileSync方法第二个参数为指定的编码参数:

  • 不指定字符编码参数时:默认返回的是一个二进制形式读取的Buffer对象。此时如果需要对文件内容进行字符串操作,需要手动将Buffer对象转换为字符串。
  • 指定字符编码为utf-8:返回的是一个以字符串形式表示的文件内容,方便进行字符串操作,而不需要手动转换Buffer对象。

因此,在读取文件时将字符编码指定为utf-8是一个好习惯。

4.2 异步

Node.js是单线程的,每个应用程序只有一个线程,如果使用同步代码会造成阻塞,因此为了实现异步行为,Node.js中会大量使用回调函数。

const fs = require("fs");

//Non-blocking,asynchronous way
fs.readFile("./txt/start.txt", "utf-8", (err, data1) => {
  if (err) return console.log("Error❌");

  fs.readFile(`./txt/${data1}.txt`, "utf-8", (err, data2) => {
    console.log(data2);
    fs.readFile("./txt/append.txt", "utf-8", (err, data3) => {
      console.log(data3);

      fs.writeFile("./txt/final.txt", `${data2}\n${data3}`, "utf-8", (err) => {
        console.log("Your file has been written");
      });
    });
  });
});
console.log("Will read file!");
  • fs.readFile方法读取名为start.txt的文件,并将文件内容作为字符串返回。回调函数接收两个参数,err是错误对象(如果读取过程中出现错误),data是文件的内容。
  • fs.writeFile方法将字符串内容写入名为final.txt的文件。回调函数是作为最后一个参数传递的,用于在写入文件操作完成后执行相应的操作。err 是一个可能包含错误信息的参数。如果写入文件操作成功,则err 参数将为 nullundefined

note:一般而言,回调函数的第一个参数通常用于接收错误信息。

5 node实现简单服务器搭建

http模块为我们提供了各种网络功能,比如搭建一个http服务器。

为了搭建一个简单的服务器,需要做两件事:创建服务器;启动服务器。

const http = require('http');

//创建
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end("Hello from the server!");
});

//启动
server.listen(8000, "127.0.0.1", () => {
  console.log("Listen to request on port 8000");
});

在上面的代码中,我们做了一些事:

  1. 通过http.createServer方法创建了一个HTTP服务器。每次有请求到达时,回调函数都会被执行。
  2. 在回调函数中,我们设置响应头为纯文本格式,并发送响应数据。
  3. 使用server.listen方法指定服务器监听的端口号(这里使用8000)。当服务器启动并开始监听请求时,会执行回调函数,并在控制台输出相应的消息。

note:ctrl^C退出服务器,ctrl^D退出REPL

6 node实现简单的路由配置

在Node.js中,可以使用内置的url模块来解析URL,并根据路径进行简单的路由配置。

const http = require('http');
const url = require('url');

// 创建服务器
const server = http.createServer((req, res) => {
  // 解析请求的URL
  const {pathname} = url.parse(req.url, true);


  // 路由处理
  if (path === '/') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello, World!');
  } else if (path === '/about') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('About page');
  } else if (path === '/login' && req.method === 'POST') {
    // 处理登录请求
    // ...
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Login successful');
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not found');
  }
});

// 监听端口
const port = 3000;
server.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

在上面的代码中:

  • req.url 是服务器接收到的客户端请求中的 URL 地址部分。它包含了请求的路径和查询参数。 例如,客户端发送的请求是 http://example.com/products?id=123,那么 req.url 的值将是 "/products?id=123"req.url 中的 URL 地址是原始的、未经过解码的形式。
  • url.parse()用于解析 URL 字符串,并返回一个包含解析后的 URL 组件的对象(第二个参数true表示返回为对象而不是字符串形式)。这个对象包含了解析后的URL的各个组成部分,一些常用属性包括:
    • pathname: URL的路径部分。
    • search: URL的查询字符串部分(包括问号)。
    • query: 解析后的查询字符串参数的对象形式(需要设置parseQueryString选项为true)。
  • 根路径'/'返回"Hello, World!",'/about'返回"About page"。对于'/login'路径,仅当请求方法为POST时才进行处理,并返回"Login successful"。对于其他路径,返回"Not found",并设置响应状态码为404。

7 node实现简单API

简单来说,API是一种服务,我们可以从中请求一些数据。

在node中构建一个简单的API,也就是当客户端请求特定route时,从server返回一些数据。可以使用Node.js的内置http模块来编写一个简单的API。

const fs = require('fs');
const http = require("http");
const url = require("url");

const server = http.createServer((req, res) => {           
  const pathname = req.url;
  if (pathname === "/" || pathname === "/overview") {
    res.end("This is overview!");
  } else if (pathname === "/product") {
    res.end("This is product!");
  } else if (pathname === "/api") {
    fs.readFile(`${__dirname}/dev-data/data.json`, "utf-8", (err, data) => {
      const productData = JSON.parse(data);
      res.writeHead(200, { "Content-type": "application/json" });
      res.end(data);
    });
  } else {
    res.writeHead(404, {
      "Content-type": "text/html",
      "my-own-header": "hello-world",
    });
    res.end("Page not found!");
  }
});

server.listen(8000, "127.0.0.1", () => {
  console.log("Listen to request on port 8000");
});

在上面的代码中,当客户端请求localhost:8000/api时,我们将读取本地${__dirname}/dev-data/data.json路径的文件,并在读取完成后执行回调函数,以json格式返回。

然而这种方式的弊端是,每次用户访问/api,服务器都会去读取-执行回调函数,也就是会多次读取。我们可以在代码执行开始就将文件读取保存,这样只需要读取一次。

const data = fs.readFileSync(`${__dirname}/dev-data/data.json`, "utf-8");
const dataObj = JSON.parse(data);

const server = http.createServer((req, res) => {
  const pathname = req.url;
  if (pathname === "/" || pathname === "/overview") {
    res.end("This is overview!");
  } else if (pathname === "/product") {
    res.end("This is product!");
  } else if (pathname === "/api") {
    res.writeHead(200, { "Content-type": "application/json" });
    res.end(data);
  } else {
    res.writeHead(404, {
      "Content-type": "text/html",
      "my-own-header": "hello-world",
    });
    res.end("Page not found!");
  }
});

server.listen(8000, "127.0.0.1", () => {
  console.log("Listen to request on port 8000");
});

note:.和__dirname的区别是,.是相对路径,所指的具体目录和当前的工作目录相关。__dirname总是返回被执行的 js脚本所在文件目录的绝对路径

8 NPM

NPM代表"Node Package Manager",是Node.js的默认软件包管理器。它是一个用于Node.js包和模块的注册表和分发系统。NPM允许开发人员在项目中查找、安装、更新和管理各种开源软件包和工具。

NPM具有以下主要功能:

  • 软件包管理
  • 依赖管理
  • 版本控制
  • 脚本管理

8.1 初始化node包

npm init

在node开发中,使用npm init会在项目中生成一个package.json文件,这个文件主要是用来记录这个项目的详细信息的,它会将我们在开发中所用到的包,已经项目的详细信息等记录在这个json文件中。方便在以后的版本迭代和项目移植时更加方便,也防止后期项目维护中误删了一个包导致项目无法正常运行的情况发生。

使用npm init初始化项目还有一个好处就是在进行项目传递的时候不需要将项目依赖包一起发送给对方,对方在接受到你的项目之后再执行npm install就可以将项目依赖全部下载到项目里.

8.2 安装node包

使用npm安装的依赖分为部署依赖开发依赖

  • 部署依赖:在开发和部署上线阶段都需要使用的包(build our code base),如express等。
  • 开发依赖:只在项目开发阶段需要用到的包(develop our application),通常是开发工具,例如webpack代码打包器、debugger工具、testing library等等。
npm i slugify             //安装部署依赖项
npm i slugify@1.0.0       //安装指定版本的包
npm i nodemon --save--dev //安装开发依赖项
npm i nodemon --global    //全局安装

当我们安装了外部node包后,会自动生成packag.jsonpackage-lock.json两个文件。

npm 5以前没有package-lock.json这个文件,需要保存依赖信息,每次安装时都要加上--save参数;npm5以后版本加入了package-lock.json文件。当安装包的时候,不需要加上--save参数,它会自动保存依赖信息,且会生成或更新package-lock.json这个文件。

二者区别:

  • package.json记录的是当前项目中你下载了哪些包(也即npm install xx 的包信息),记录了你下载的包信息(地址、版本号等),不包含依赖包信息。

  • package-lock.json文件记录的是当前项目中你下载了哪些包以及你下载的这些包的各种依赖包信息,包括地址、版本号等。主要作用有以下两点:当删除node_module目录时,想通过npm install 恢复所有包时,提升下载速度;锁定版本号,防止自动升级新版本

8.3 其他

//卸载node包
npm uninstall slugify

//查看过时的包
npm outdated

//更新包
npm undate slugify

9 总结

Nodejs是现代Web开发中的重要技术之一,本文仅介绍了Nodejs的基础应用,在构建大型项目时,我们往往会使用基于Node的框架如Express。实际上除了读写文件,其他node可以实现的功能如搭建服务器、编写API、进行路由配置等,在express中都提供了更为简洁的步骤来实现。