ExpressJS

ExpressJS

Express 是一个简洁而灵活的 Node.js Web 应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。

使用 Express 可以快速地搭建一个完整功能的网站,但是,我们一般更倾向于使用 Express 来快速搭建网站后端

前置知识

  • JavaScript 语言
  • Node.js 相关知识,包括 npm / yarn 的使用
  • RESTful API 的有关知识(事实上后文也有介绍) https://www.runoob.com/nodejs/nodejs-restful-api.html
  • (至少一种)数据库的使用操作
    • 可以是直接使用 SQL 语句操作
    • 可以是使用 ORM 来操作

初始化项目

搜索结果可以找到的大多数编写教程均是基于 CommonJS 规范(以 requiredefine 为特点),而 Node.js 则推出了基于语言层面支持的 ES6 Module 规范(以 importexport 为特点)。后者必将成为今后较为常用的编写规范。如果想了解更多关于 Javascript 模块化开发的有关知识,可以参考本文初稿作者的这篇博客

同样,这里我们采用更为广泛使用的 yarn 来进行包管理。其安装在 Node.js 简介及 npm 的介绍中已给出,这里我们不再赘述。

首先我们先新建一个项目。(这里采用 Windows 的 CMD 进行操作,Linux 的操作类似,下同)

D:\Coding>cd D:\Coding\MyDashBoardBackend

D:\Coding\MyDashBoardBackend>yarn init
yarn init v1.22.17
question name (MyDashBoardBackend):
question version (1.0.0):
question description: A sample backend project.
question entry point (index.js):
question repository url:
question author:
question license (MIT):
question private:
success Saved package.json
Done in 20.93s.

然后我们安装 express 模块:

yarn add express

为了使用 ES6 Module 规范,我们配置 package.json,在其中加入 "type": "module" 字段:

{
  "name": "MyDashBoardBackend",
  "version": "1.0.0",
  "type": "module",
  "description": "A sample backend project.",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "express": "^4.17.2"
  }
}

然后,我们在根目录新建 index.js,写入如下内容:

import express from "express";
const app = express()

app.get('/', (req, res) => {
    res.send('Hello, baka c7w!')
})

app.listen(3000);

然后我们就可以运行 node index.js,然后前往浏览器访问 http://localhost:3000/,便可以看到我们输出的欢迎信息。

若是感觉如此逼格还不够高,不要紧,我们可以继续进行一些配置:

我们可以继续修改 package.json,向其中加入使用 yarn start 开启服务端的配置:

{
  "name": "MyDashBoardBackend",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node index.js"
  },
  "description": "A sample backend project.",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "express": "^4.17.2"
  }
}

之后我们就可以使用 yarn start 来开启服务端了。

路由 Routing

没错,相信你已经猜到了,我们后端网站的路由便是主要通过 app.getapp.post 两个方法来定义。事实上,如果你对 HTTP 请求方法了解的更多些的话,我们可以使用以下方法:

  • app.get()
  • app.post()
  • app.put()
  • app.delete()

事实上还有更多,这里我们不再一一列出,如欲了解可以借助官方的 Docs

我们这里给出一些示例写法:

import express from "express";
const app = express()

app.get('/', (req, res) => {
    res.send('Hello, baka c7w!');
})

app.get('/item', (req, res) => {
    res.send('GET method to /item');
})

app.post('/item', (req, res) => {
    res.send('POST method to /item');
})

// Regular Expression!
app.get('/item/*', (req, res) => {
    console.debug(req.path); // `/item/1`
    console.debug(req.params); // undefined
    res.send("Item/*")
})


// Variables!
app.get('/item2/:item/', (req, res) => {
    console.debug(req.path); // `/item2/1`
    console.debug(req.params); // { item: '1' }
    res.send("Item/*")
})

// Even ReExp + Variables!
app.get("/item3/:item(\\d+)", (req, res) => {
    console.debug(req.path); // `/item3/1`
    console.debug(req.params); // { item: '1' }
    res.send("Item/*")
})

app.listen(3000);

Route Handler

我们可以尝试考虑以下情景:我们要根据用户身份(这里做简要简化,假设用户身份通过 Routing 传入),分别展示不同的页面。事实上这种情景很常见,比如一个网站的管理员登入博客看到的应该是管理页面,而登录作者看到的应该是写作页面,普通用户看到的就是浏览页面。但是这些页面中又会有一些耦合的元素。于是,我们能不能采用 OOP 课程中策略模式的思想来解决这个问题呢?

const logger = (req, res, next) => { // Logger 部件,记录用
    console.debug(req.params.user + ' tried to visit...');
    next();
}

const main = (req, res, next) => {
    if(req.params.user == "c7w") {
        res.result = "Welcome!<br />";
        next();
    } else {
        res.send("Permission Denied")
    }
}

const footer = (req, res) => {
    res.result += ('<br /><br />' + new Date());
    res.send(res.result)
}


app.get("/user/:user", [logger, main, footer])

事实上我们可以利用这种 Handler “数组”来定义处理一个请求的先后顺序。如果在前面的某个 Handler 调用了 next(),那么便会执行下一个 Handler

req 与 res

这里我们介绍下 reqres 两个参数的主要属性和方法。

req

PropertyDescription
req.query在请求后以 ?& 连接的键值对
req.body请求体的内容
req.cookiesCookies 中的内容

更多详见:https://expressjs.com/en/4x/api.html#req

res

MethodDescription
res.download()Prompt a file to be downloaded.
res.end()End the response process.
res.json()Send a JSON response.
res.redirect()Redirect a request.
res.render()Render a view template.
res.send()Send a response of various types.
res.sendFile()Send a file as an octet stream.
res.sendStatus()Set the response status code and send its string representation as the response body.

更多详见:https://expressjs.com/en/4x/api.html#res

Middleware

Middleware(中间件函数)的概念其实很好理解。就像是我们上面举的那个例子,loggermain 就可以理解成是 Middleware functions。中间件函数是带有了 req, res, next 为签名的函数,在 Express 处理请求的时候,事实上会将某个地址对应的路由的所有中间件组织成一个类似链表的结构,随着 next() 的调用在中间件间顺序执行。而且,中间件都是有修改 reqres 的所有属性的能力的。

一个需要注意的点是,如果中间件函数没有使用类似于 res.send() 的方法将一个请求返回的话,必须要调用 next() 函数,不然即使这个中间件函数执行到末尾,也不会自动跳转。这就会导致请求“假死”的现象。

全局 Middleware

使用 app.use([path], <middleware function>) 我们可以添加供全局使用的 Middleware 函数(Application-level middleware),示例如下:

import express from "express";
const app = express()

const logger = (req, res, next) => { // Logger 部件,记录用
    console.debug(`[${new Date()}] ${req.method} ${req.originalUrl}`);
    next();
}



app.get('/', (req, res) => {
    res.send('Hello, baka c7w!');
})

app.get('/item', (req, res) => {
    res.send('GET method to /item');
})

app.post('/item', (req, res) => {
    res.send('POST method to /item');
})

app.get('/item/*', (req, res) => {
    console.debug(req.path); // `/item/1`
    console.debug(req.params); // undefined
    res.send("Item/*")
})

app.get('/item2/:item/', (req, res) => {
    console.debug(req.path); // `/item2/1`
    console.debug(req.params); // { item: '1' }
    res.send("Item/*")
})

app.get("/item3/:item(\\d+)", (req, res) => {
    console.debug(req.path); // `/item3/1`
    console.debug(req.params); // { item: '1' }
    res.send("Item/*")
})

app.use(logger)

app.listen(3000);
[Tue Feb 08 2022 22:16:18 GMT+0800 (China Standard Time)] GET /user/123
[Tue Feb 08 2022 22:16:20 GMT+0800 (China Standard Time)] GET /user/123
[Tue Feb 08 2022 22:16:22 GMT+0800 (China Standard Time)] GET /user/123/123

异常处理 Middleware

我们可以定义处理异常的中间件函数,方法如下:

app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Sorry, but fatal error occurred meanwhile.')
})

内置 Middleware

Express 内置了以下中间件:

对于 POST 请求,我们推荐使用后两个中间件。

CRUD 的实现

作为轮子工厂,我们经过简单的搜索就能找到 JavaScript 与 mysql 连接的工具,其使用教程可以见此

后续拓展

  • 了解更多 ExpressJS 的中间件
  • 了解 ExpressJS 的模板渲染机制
  • 换用其他数据库尝试,包括 fs(本地存储),MySQL,以及非关系型数据库 PostgreSQL

可以应用 ExpressJS 的课程:

  • 《软件工程》

资源链接