node.js基础

397 阅读18分钟

在node环境下运行脚本文件

image-20210929225708522

终端中执行命令运行js,打印出结果

node run.js

安装nodemon来监视js文件的变化,自动运行,这样每次修改js,就会自动运行

mpm i -g nodemon
nodemon run.js
image-20210929231904767

node中的异步编程

node.js 异步编程的直接提现在回调函数上

异步:不等待 非阻塞 非阻塞式IO操作

优点:性能高,处理大量的并发请求

案例

一边读取文件,一边执行其他命令,读文件的目的 → 读取文件的内容 → 回调函数中返回

  • 阻塞代码使用 fs( fileSystem )模块 commonjs
// 解析sf模块 - 文件系统模块,负责读写文件
const fs = require('fs')
//sf模块调用同步读取文件方法,并传入文件路径作为参数
const data = fs.readFileSync('./01-runnode.js')

console.log(data.toString());
console.log('hhh');

//-----------------
const str = ``;
let num = 0;
//....
console.log('maxuan');
hhh

必须先输出文件字符串,再输出hhh

  • 非阻塞代码,大大提高了程序的性能
const fs = require('fs')
//fs模块调用异步读取文件方法,并传入文件路径和读取完成后的回调函数,回调函数会接收两个参数,错误和数据
fs.readFile('./01-runnode.js', function(err,data) {
  // 在node编程中有个错误优先机制,回调函数的参数第一个为err
  if(err) {
    console.log(err.stack);
    return
  }
  console.log(data.toString());
})

console.log('先被执行');

/*------------
先被执行
01-runnode.js内容...
*/

非阻塞代码在读取文件的期间,继续运行了下面的代码,输出了在代码最后的继续执行,再回调打印的文件内容

这样的代码就叫做异步编程 setTimeout 也是异步编程

因为阻塞是按顺序执行的,而非阻塞是不需要按顺序执行的,这就需要回调函数来执行后面的命令

使用 promise 操作

const fs = require('fs')
const { promisify } = require('util')
// 通过 promisify 将 fs.readFile包装成promise对象
const readFile = promisify(fs.readFile) 
// 接下来进行异步编程
async function asyncReadFile() {
  try {
    const data = await readFile('./01_runnode.js')
    console.log(data.toString())
  } catch (error) {
    // error.stack 属性是一个字符串,描述代码中 error被实例化的位置
    console.log(error.stack);
  }
}
asyncReadFile()
console.log('我先被调用');
/*
我先被调用
01_runnode.js内容...
*/

使用 generator (生成器) 操作 — ES6知识

const fs = require('fs')
const { promisify } = require('util')
// 通过 promisify 将 fs.readFile包装成promise对象
const readFile = promisify(fs.readFile) 

function* read() {
  yield readFile('./01_runnode.js')
}
let ge = read();
ge.next().value.then((data) => {
  console.log(data.toString())
}).catch((err) => {
  console.log(err);
})

console.log('我先执行');
/*
我先被调用
01_runnode.js内容...
*/
  • yield是ES6的新关键字,使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的return关键字

  • yield关键字实际返回一个IteratorResult(迭代器)对象,它有两个属性,value和done,分别代表返回值和是否完成

  • yield无法单独工作,需要配合generator(生成器)的其他函数,如next,懒汉式操作,展现强大的主动控制特性

深入理解js中的yield - 简书

Buffer对象的相关操作

第一个参数 — 文件路径

第二个参数 — 编码格式

第三个参数 — 回调函数

const fs = require('fs')
fs.readFile('./01-runnode.js', 'utf-8',function(err,data) {
  //...
})

不传入'utf-8'时,所打印的是个十六进制的 buffer 对象,添加这个data直接打印的结果就是字符串,无需toString()方法

数据创建

// 创建一个长度为10、且用0填充的buffer对象
const buf1 = Buffer.alloc(10)
console.log(buf1); // <Buffer 00 00 00 00 00 00 00 00 00 00>

// 创建一个Buffer对象并传入字符串 Hellow World!
const buf2 = Buffer.from('Hello World!')
console.log(buf2) // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64 21>

// 创建对象冰川乳一个数组
const buf3 = Buffer.from([1,2,3])
console.log(buf3) // <Buffer 01 02 03>

数据写入

// buffer中写入 hello,会从前向后写入对应16进制编码
buf1.write('hello')
console.log(buf1)// <Buffer 68 65 6c 6c 6f 00 00 00 00 00>

// buffer中写入 hello buffer,重复的写入会覆盖之前的内容,而且长度超过的部分会因为buffer容量不够而被忽略
buf1.write('hello buffer') 
console.log(buf1)// <Buffer 68 65 6c 6c 6f 20 62 75 66 66>

数据读取

// 默认读取为utf-8的数据,可写可不写
console.log(buf1.toString()); // hello buff

// 传入参数转换为指定格式数据
console.log(buf1.toString('base64')); // aGVsbG8gYnVmZg=

数据合并

const buf4 = Buffer.concat([buf1, buf2])
console.log(buf4.toString()) // hello buffHello World!

Stream管道流操作

流的类型

Node.js 中有四种基本的流类型:

流的操作

const fs = require('fs')

// 创建可读流 —— 对应读取目标文件
const readerStream = fs.createReadStream('./readme.md')

// 创建可写流 —— 对应小写入的目标文件,如果不存在将被创建
const writerStream = fs.createWriteStream('./test.txt')

// 通过管道方法进行传输
// 对可读流调用 pipe() 方法,并传入可写流
readerStream.pipe(writerStream)
console.log('执行完毕');
image-20211001164725565

zlib (压缩)

zlib模块提供通过 Gzip 和 Deflate/Inflate 实现的压缩功能,Brotli 也是如此

createGzip() 压缩

// 引入fs模块
const fs =require('fs')
// 引入zlib模块
const zlib = require('zlib')
// 创建gzip压缩对象
const gzip = zlib.createGzip()

const txt = fs.createReadStream('./test.txt')
const zip = fs.createWriteStream('./test.zip')

txt.pipe(gzip).pipe(zip)
console.log('压缩完成');
image-20211001200902347

以上链式调用可以直接用一行代码

fs.createReadStream('./test.txt').pipe(gzip).pipe(fs.createWriteStream('./test.zip'))
console.log('压缩完成');

生成的压缩包内的文件,是没有扩展名的

解压操作会添加

createGunzip 解压

// 引入fs模块
const fs =require('fs')
// 引入zlib模块
const zlib = require('zlib')
// 链式调用
fs.createReadStream('./test.zip').pipe(zlib.createGunzip()).pipe(fs.createWriteStream('./test.txt'))
console.log('解压完成');

事件循环和驱动程序 (了解)

事件循环

Node.js 是单线程应用程序,但是通过事件和回调支持并发,所以性能非常高

Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发

Node.js 基本上所有的事件机制都是用设计模式中的观察者模式实现

Node.js 单线程类似进入一while(true)的事件循环,直到没有事件观察者时退出,每个异步事件都生成一个事件观察者,如果有时间发生就调用该回调函数

事件驱动程序

Node.js 使用事件驱动模型,当 web server 接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求

这个请求完成时,他被放回处理队列,当达到队列开头,这个结果被返回给用户

这个模型非常高效可扩展性非常强,因为 web server 一直接受请求二不等待任何读写操作,这也被称之为非阻塞式IO或者事件驱动IO

在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数

image-20211001223236632

整个事件驱动的流程就是这么实现的,非常简洁,有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的函数相当于观察者(Observer)

events实例:

Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter类 来绑定和监听事件,下面就是一个例子

// 引⼊event 模块
const events = require("events");
// 创建eventEmitter对象
const eventEmitter = new events.EventEmitter();
// 创建事件处理程序


// 绑定 connection 事件处理程序
eventEmitter.on("connection", function(x) {
  console.log("连接成功" + x);
  // 触发 data_received事件
  eventEmitter.emit("data_received", x);
});

// 使⽤匿名函数绑定 data_received 事件
eventEmitter.on("data_received", function (x) {
  console.log("数据接收成功" + x);
});

const x = 'x'
// 触发 connection事件
eventEmitter.emit("connection", x);
console.log("程序执⾏完毕");
连接成功x
数据接收成功x
程序执⾏完毕

上方的事件和函数,实际上是从下方开始触发的,触发conn事件,并传入一个参数,让预先定义的事件函数运行起来,主题事件内触发另一个事件,来执行外部绑定的另一个事件的回调函数,来处理这个事件传入的值,直到事件完成,打印 程序执行完毕

CommonJS规范以及模块系统

common.js写法

命名一个hello.js文件作为模块来存储函数和对象

exports.name = function() {
  console.log('Max');
}

exports.high = function() {
  console.log('175cm');
}

exports.size = '14cm'

exports.arr = [1,2,3,4,5]

exports.object = {
  x: 1,
  y: 2,
  z: 3
}

node.js 提供了 exports 和 require 两个对象,其中 exports 是模块公开的接口,require 是用于从外部获取一个模块的接口

这里通过require,因为模块并不是nodejs内置的,所以值为路径,并且不需要写扩展名 .js

const obj = require('./hello')

console.log(obj)
obj.name()
obj.high()
console.log(obj.size);

通过obj.xxx来调用模块内的函数和变量

{
  name: [Function],
  high: [Function],
  size: '14cm',
  arr: [ 1, 2, 3, 4, 5 ],
  object: { x: 1, y: 2, z: 3 }
}
Max
175cm
14cm

module 写法

抛出函数写法

function Dog() {
  let name;
  this.setName = function(myName) {
    name = myName
  }
  this.sayName = function() {
    console.log('hello ' + name);
  }
}

module.exports = Dog

如果是 module.exports 抛出的对象,new 出来的对象,就是这个对象本身,可以直接通过new出来的变量d1运行这个模块,或者d1.xxx 来调用对象内部的方法或变量,这取决于抛出的到底是函数还是对象

如果是 exports 抛出的对象,它是被挂载到了同一个对象上,需要.xxx来引用抛出的对象上的所有方法和对象

const Dog = require('./module')

const d1 = new Dog()

d1.setName('Max')
d1.sayName()

模块抛出对象的写法

const  Dog = {
  name: 'Max',
  setName(myName) {
    this.name = myName
  },
  sayName() {
    console.log('hello ' + this.name);
  }
}

module.exports = Dog

如果抛出的是对象,便不再需要new获取这个对象

const Dog = require('./module')

// console.log(Dog.name);
Dog.setName('xxxx')
Dog.sayName()

模块加载策略

Node.js的 require 方法中的文件查找机制如下

  • fs/http/url/path等原生模块
  • ./module1 相对路径模块
  • /pathmodule/module1 绝对路径的文件模块
  • module1 非原生模块的文件模块

由于 Node.js 中存在4类模块儿(原生模块儿和三种文件模块),尽管 require 方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同,如下图所示

image-20211002084644488

常用内置模块

fs 文件系统 | Node.js API 文档 (nodejs.cn)

fs 模块 — 只做了解

fs存储数据文件过大占用内存高,导致系统崩溃,将来的应用是要把数据存到数据库,所以这段只做了解

异步和同步

打开文件

fs.open(path, flags[, mode] callback)

参数:

  • path — 文件的路径
  • flags — 文件打开的行为,具体见下面
  • mode — 设置文件模式(权限),文件创建默认权限为 0666(可读,可写)
  • callback — 回调函数,带有两个参数,如 callabck(err, fd)

flags 参数可以是以下值:

  • 'a': 打开文件进行追加。 如果文件不存在,则创建该文件。

  • 'ax': 类似于 'a' 但如果路径存在则失败。

  • 'a+': 打开文件进行读取和追加。 如果文件不存在,则创建该文件。

  • 'ax+': 类似于 'a+' 但如果路径存在则失败。

  • 'as': 以同步模式打开文件进行追加。 如果文件不存在,则创建该文件。

  • 'as+': 以同步模式打开文件进行读取和追加。 如果文件不存在,则创建该文件。

  • 'r': 打开文件进行读取。 如果文件不存在,则会发生异常。

  • 'r+': 打开文件进行读写。 如果文件不存在,则会发生异常。

  • 'rs+': 以同步模式打开文件进行读写。 指示操作系统绕过本地文件系统缓存。

    这主要用于在 NFS 挂载上打开文件,因为它允许跳过可能过时的本地缓存。 它对 I/O 性能有非常实际的影响,因此除非需要,否则不建议使用此标志。

    这不会将 fs.open()fsPromises.open() 变成同步阻塞调用。 如果需要同步操作,应该使用类似 fs.openSync() 的东西。

  • 'w': 打开文件进行写入。 创建(如果它不存在)或截断(如果它存在)该文件。

  • 'wx': 类似于 'w' 但如果路径存在则失败。

  • 'w+': 打开文件进行读写。 创建(如果它不存在)或截断(如果它存在)该文件。

  • 'wx+': 类似于 'w+' 但如果路径存在则失败。

os模块 — 只做了解

os 操作系统 | Node.js API 文档 (nodejs.cn)

操作系统模块

const os = require("os");
// cpu的字节序
console.log("endianness : " + os.endianness());
// 操作系统名
console.log("type : " + os.type());
// 系统内存总量
console.log("endianness : " + os.totalmem() + " bytes");
// 操作系统空闲内存量
console.log("endianness : " + os.freemem() + " bytes");

打印结果

endianness : LE
type : Windows_NT
endianness : 68667461632 bytes
endianness : 52082888704 bytes

url 模块

url 网址 | Node.js API 文档 (nodejs.cn)

当前最新的node中,使用 WHATWG 的 API 解析 URL 字符串

const url = new URL('http://max:123@maxlearn.tom:8080/user/123/?query=name#xxx')

console.log(url);

使用遗留的 API 解析 URL 字符串

const url = require('url')
const myUrl = url.parse('http://max:123@maxlearn.tom:8080/user/123/?query=name#xxx')
console.log(myURL)

打印这个url对象,会发现这个链接被URL构造函数解析为了一个包含各种属性值的对象

URL {
  href: 'http://max:123@maxlearn.tom:8080/user/123/?query=name#xxx',
  origin: 'http://maxlearn.tom:8080',
  protocol: 'http:',
  username: 'max',
  password: '123',
  host: 'maxlearn.tom:8080',
  hostname: 'maxlearn.tom',
  port: '8080',
  pathname: '/user/123/',
  search: '?query=name',
  searchParams: URLSearchParams { 'query' => 'name' },
  hash: '#xxx'
}

path 模块

path 路径 | Node.js API 文档 (nodejs.cn)

path模块着重记住绝对路径获取方法就可以了,以下获取文件绝对路径的两个方法,可以互换使用

const path = require('path')
// __dirname 获取当前项目的绝对路径的目录名字
console.log(path.join(__dirname,'./10_path.js'))
// C:\Users\Maxuan\Desktop\NODEJS\10_path.js

// 将相对路径转换为绝对路径, path.resolve() 相当于 path.join(__dirname)
console.log(path.resolve('./10_path.js'));
// C:\Users\Maxuan\Desktop\NODEJS\10_path.js

// 获取路径文件中的后缀名
console.log(path.extname('./11_path.js'));
// .js

net 模块

net 网络 | Node.js API 文档 (nodejs.cn)

dns模块

dns 域名服务器 | Node.js API 文档 (nodejs.cn)

domain模块

domain 域 | Node.js API 文档 (nodejs.cn)

error模块

Error 错误 | Node.js API 文档 (nodejs.cn)

global全局变量

global 全局变量 | Node.js API 文档 (nodejs.cn)

这些对象在所有模块中都可用。 以下变量可能看起来是全局的,但实际上不是。 它们只存在于模块的作用域中,参见模块系统文档

此处列出的对象特定于 Node.js。 有些内置对象是 JavaScript 语言本身的一部分,它们也可以全局地访问

http模块 — 搭建服务器 — 重点

理解内部的处理原理

创建http服务器

新建一个http.js模块

// 引入http模块
const http = require('http')
// 创建appServer对象 request(请求) response(响应)
const app = http.createServer((req, res) => {
  res.end('hello node.js')
})

// 监听端口号 http://localhost:3000/
app.listen(3000)

运行当前模块

nodemon http.js

打开浏览器输入localhost:3000 会发现浏览器中出现了返回的字符串

image-20211002170600401
不同的url做不同的响应处理

创建的服务器实例,我们打印req,会获得一个请求对象,保存模块不会打印出新内容

const app = http.createServer((req, res) => {
  console.log(req)
  res.end('hello node.js')
})

但当修改网页中的地址并回车的时候

image-20211002171427425

终端会打印出这个请求对象,内容长达上千行,终端容不下会覆盖掉

其中比较重要的,就有url属性和方法属性 ( 地址栏发起的请求都属于GET请求 )

image-20211002172829253

接下来就可以通过url来

// 引入http 和 fs模块
const http = require('http')
const fs = require('fs')

const app = http.createServer((req, res) => {

  const { url, method } = req
  if(url === '/index' && method === 'GET')  {
    // 返回一个首页给浏览器
    fs.readFile('./static/index.html', (err, data) =>{
      // 当文件路径读取错误
      if(err){
        // 设置状态码,如果不设置状态码,页面虽然没有请求成功,仍然不会报红
        res.statusCode = 500
        // 返回内容
        res.end('500 - Interval Serval Error!')
      }
      res.statusCode = 200
      // 限定返回的文件必须为html格式
      res.setHeader('Content-Type', 'text/html')
      res.end(data)
    })
  } else if(url === '/about' && method === 'GET'){
    
  }else {
    res.end('hello node.js')
  }
  
})

// 监听端口号 http://localhost:3000/
app.listen(3000)
错误响应
  • 设置状态码

if (err) { }的条件判断,规范的写法是要更改状态码,这样响应出错才会被浏览器报红

image-20211002191332172image-20211002191942931

  • 设置返回文件格式

在返回数据之前可以设置头文件格式为html,让浏览器按照该格式读取

// 限定返回的文件必须为html格式读取
res.setHeader('Content-Type', 'text/html')
res.end(data)
image-20211002192813715

当设置响应格式为css,返回的数据就会被当做css解析导致错误

res.setHeader('Content-Type', 'text/css')
res.end(data)
image-20211002193521018

所以一般情况下,不要去指定格式

  • 设置其他格式的请求

假如我们设置一个图片的请求,当前的路径是正确的

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>关于我们</title>
</head>
<body>
  <h1>这是关于我们页面</h1>
  <a href="index"><button>主页</button></a>
  <a href="about"><button>关于我们</button></a>
  <!-- 写入一个img的请求 -->
  <img src="/static/img/1.jpg" alt="">
</body>
</html>

返回的状态也没有报错,读取路径也是对的,但是图片就是没有加载,这是因为后端没有对这个请求作出响应

image-20211002200945852

以往用这么写没有报错,是因为服务器已经帮忙处理了这个请求,当我们自己写node.js服务器的时候,就需要手动配置了

这里需要增加一个条件判断,当浏览器请求了一个,让服务器作出响应

 else if(req.headers.accept.indexOf('image/*') !== -1 && method === 'GET') {
    console.log(url);
    // 流的方式返回数据
    fs.createReadStream(__dirname + url).pipe(res)
  } 

req.headers.accept 返回的是一个字符串

image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8/static/img/1.jpg

通过调用这个字符串的indexOf()方法来确定image/*在这个字符串中出现的位置,未匹配到返回 -1,所以要 !== -1来得到 true

通过流方式返回 绝对路径 + url 的拼接字符串来得到服务器上的正确地址,并通过pipe() 写入到res

  • 读取json

添加条件

 else if(url === '/user' && method === 'GET'){
   res.statusCode = 200;
   res.setHeader('Content-Type', 'application/json');
   res.end(JSON.stringify([{name:'Max'}]));
 }
image-20211002210024028

express框架快速上手

以上使用http、fs模块,对请求的处理,以及对错误的响应都非常的复杂,对于一个项目成百上千的请求,不可能做到不出问题

所以就有团队开发出了 express框架,虽然这个框架现在已经停止更新,但是现在用这个来搭建一个简易的小型服务器也是非常便捷的

初始化

在根目录 npm init

npm init

/* 描述
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
*/
package name: (nodejs)
version: (1.0.0) 1.0.1
description:
entry point: (01_runnode.js) index.js
test command:
git repository:
keywords: Max                                                                                                                                                                                            
author: Max
license: (ISC)                                                                                                                                                                                           
About to write to C:\Users\Maxuan\Desktop\NODEJS\package.json:

{
  "name": "nodejs",
  "version": "1.0.1",
  "description": "-vue\r   -vue全家桶",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "Max"
  ],
  "author": "Max",
  "license": "ISC"
}


Is this OK? (yes)

输入yes创建json配置文件

或者直接 一条命令创建

npm init --yes

Wrote to C:\Users\Maxuan\Desktop\NODEJS\package.json:

{
  "name": "NODEJS",
  "version": "1.0.0",
  "description": "-vue\r   -vue全家桶",
  "main": "01_runnode.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

创建的json配置文件,如果需要也可以后期修改

image-20211002220033695

安装express

npm i express -save

hello world example

// 引入 express 模块
const express = require('express')
// 创建 app 实例
const app = express()
// 监听端口
const port = 3000

// 匹配的路由地址
app.get('/', (req, res) => {
  res.send('Hello World!')
})

// app设置监听端口,服务器创建完成后的回调函数 —— 打印这条信息
app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

终端输入命令 nodemon 12_express.js 运行模块

nodemon 12_express.js
Example app listening at http://localhost:3000

这时访问 localhost:3000 就会请求/路由做出响应,返回 Hello World!

image-20211002221925030

应用例子

// 引入 express、fs、path 模块
const express = require('express')
const fs = require('fs')
const path = require('path')

// 创建 app 实例
const app = express()
// 监听端口
const port = 3000

// 匹配的路由地址
app.get('/', (req, res) => {
  res.send('Hello World!')
})
// 处理首页请求
app.get('/index', (req, res) => {
  fs.readFile('./static/index.html', 'utf-8', (err, data) => {
    if (err) {
      res.statusCode = 500
      res.end('500 - Interval Serval Erorr!')
    }
    res.statusCode = 200
    res.setHeader('Content-Type', 'text/html')
    res.end(data)
  })
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

接下来的使用就是在模块内添加app.get('/xxx', (req, res) => { }) 来完成对各个页面路由的请求了

下面的则是一个处理页面对文件加载的请求

// 匹配所有路由,解决加载其余全部文件的请求
app.get('*', (req, res) => {
  //res.setHeader('Content-Type', 'image/*')
  fs.readFile(path.join(__dirname, req.url), (err, data) => {
    if (err) {
      throw err
    }
    res.send(data)
  })
})

在 express框架内path.resolve(req.url) 和 path.join(__dirname, req.url) 表现是不一样的

  • resolve返回的是服务器的路径

    C:\static\img\1.jpg

  • join(__dirname,) 返回的是电脑上的路径

    C:\Users\Maxuan\Desktop\NODEJS\static\img\1.jpg

这里是在电脑上运行,所以用join就好了

express生成器

如果一个项目过大,那么把所有的路由请求都放在一个js内,就显得杂乱臃肿了

为了解决这个问题 express 提供了一个 Express 应用程序生成器 - Express 中文文档 | Express 中文网 (expressjs.com.cn)

通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。

你可以通过 npx (包含在 Node.js 8.2.0 及更高版本中)命令来运行 Express 应用程序生成器。

$ npx express-generator

对于较老的 Node 版本,请通过 npm 将 Express 应用程序生成器安装到全局环境中并执行即可。

$ npm install -g express-generator
$ express

-h 参数可以列出所有可用的命令行参数:

$ express -h

  Usage: express [options] [dir]

  Options:

    -h, --help          输出使用方法
        --version       输出版本号
    -e, --ejs           添加对 ejs 模板引擎的支持
        --hbs           添加对 handlebars 模板引擎的支持
        --pug           添加对 pug 模板引擎的支持
    -H, --hogan         添加对 hogan.js 模板引擎的支持
        --no-view       创建不带视图引擎的项目
    -v, --view <engine> 添加对视图引擎(view) <engine> 的支持 (ejs|hbs|hjs|jade|pug|twig|vash) (默认是 jade 模板引擎)
    -c, --css <engine>  添加样式表引擎 <engine> 的支持 (less|stylus|compass|sass) (默认是普通的 css 文件)
        --git           添加 .gitignore
    -f, --force         强制在非空目录下创建

例如,如下命令创建了一个名称为 myapp 的 Express 应用。此应用将在当前目录下的 myapp 目录中创建,并且设置为使用 Pug 模板引擎(view engine):

$ express --view=pug myapp

   create : myapp
   create : myapp/package.json
   create : myapp/app.js
   create : myapp/public
   create : myapp/public/javascripts
   create : myapp/public/images
   create : myapp/routes
   create : myapp/routes/index.js
   create : myapp/routes/users.js
   create : myapp/public/stylesheets
   create : myapp/public/stylesheets/style.css
   create : myapp/views
   create : myapp/views/index.pug
   create : myapp/views/layout.pug
   create : myapp/views/error.pug
   create : myapp/bin
   create : myapp/bin/www

然后安装所有依赖包:

$ cd myapp
$ npm install

在 MacOS 或 Linux 中,通过如下命令启动此应用:

$ DEBUG=myapp:* npm start

在 Windows 命令行中,使用如下命令:

> set DEBUG=myapp:* & npm start

在 Windows 的 PowerShell 中,使用如下命令:

PS> $env:DEBUG='myapp:*'; npm start

然后在浏览器中打开 http://localhost:3000/ 网址就可以看到这个应用了。

通过生成器创建的应用一般都有如下目录结构:

.
├── app.js // 入口文件
├── bin
│   └── www // 整个入口,会加载app.js
├── package.json // 配置文件
├── public // 静态资源
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes // 路由
│   ├── index.js // 访问 index 的时候会加载index.js内的路由
│   └── users.js // 访问 user 的时候会加载user.js内的路由
└── views // 页面文件,在后端语言中不是用html来写页面,而是像pug这类模板语言来进行处理
    ├── error.pug
    ├── index.pug
    └── layout.pug

7 directories, 9 files

如果需要还可以建立data、db来存放数据文件

通过 Express 应用生成器创建应用只是众多方法中的一种。你可以不使用它,也可以修改它让它符合你的需求。

Express 5.x - API Reference - Express 中文文档 | Express 中文网 (expressjs.com.cn)