JS 流行框架(二):Express
Express 是一个基于 NodeJS 的 Web Server 开发框架,可以帮助我们省略绝大部分繁琐且无技术含量的步骤、快速地构建 Web 服务器
基本使用
在使用 Express 之前,必须先下载 express,示例如下:
npm install express --save
 
在 express 下载完成之后,就可以非常快速地创建一个服务器应用程序,示例如下:
// 1. 导入 express 模块
const express = require('express');
// 2. 创建 express 实例
const app = express();
// 利用 express 实例处理 get 请求
app.get('/express/get', (req, res, next) => {
  res.end('Hello Express!');
});
// 利用 express 实例处理 post 请求
app.post('/express/post', (req, res, next) => {
  res.end('Hello Express!');
});
// 3. 利用 express 实例监听端口
app.listen(3000, () => {
  console.log('Listen Success');  // Listen Success
});
 
 
网页
静态资源
示例如下:
// 利用 express 实例返回静态资源
app.use(express.static(path.join(__dirname, 'public')));
 
动态资源
示例如下:
// 利用 express 实例返回动态资源
/* 1. 告诉 express 动态资源存储在哪里 */
app.set('views', path.join(__dirname, 'views'));
/* 2. 告诉 express 动态资源利用哪一种模板引擎 */
app.set('view engine', 'ejs');
/* 3. 匹配路由,将渲染之后的动态资源返回 */
app.get('/', (req, res, next) => {
  res.render('login', {
    username: 'Reyn Morales',
    password: '1024'
  });
});
 
 
路由
非模块化
通常情况下,通过 express 实例的 get 和 post 方法就实现相应的路由,示例如下:
// 利用 express 实例处理 get 请求
app.get('api/goods/list', (req, res, next) => {
  res.end('GET: api/goods/list');
});
app.get('api/user/info', (req, res, next) => {
  res.json({
    username: 'Reyn Morales',
    age: 21,
    gender: 'Male',
    method: 'GET'
  });
});
// 利用 express 实例处理 post 请求
app.post('api/goods/new', (req, res, next) => {
  res.end('POST: api/goods/new');
});
app.post('api/user/login', (req, res, next) => {
  res.json({
    username: 'Reyn Morales',
    password: '1024',
    method: 'POST'
  })
});
 
模块化
实际上,也可以通过 Express 的 Router 实例以模块化的方式路由,示例如下:
- /router/user
 
// 1. 导入 express 模块
const express = require('express');
// 2. 利用 express 的 Router 方法创建 router 实例
const router = express.Router();
// 利用 router 的 get 方法实现路由
router.get('/info', (req, res, next) => {
  res.json({
    username: 'Reyn Morales',
    age: 21,
    gender: 'Male',
    method: 'GET'
  });
});
// 利用 router 的 post 方法实现路由
router.post('/login', (req, res, next) => {
  res.json({
    username: 'Reyn Morales',
    password: '1024',
    method: 'POST'
  })
});
// 3. 导出 user 相关的 router
module.exports = router;
 
- /app.js
 
// 4. 导入 user 相关的 router
const userRouter = require('./router/user');
// 5. 利用 router 处理路由
app.use('/api/user', userRouter);
 
参数
get 请求
示例如下:
// 获取 get 请求参数
app.get('/api/user/info', (req, res, next) => {
  console.log(req.query); /* { username: 'reyn' } */
});
 
 
post 请求
示例如下:
// 获取 post 请求参数
/* 1. 告诉 express 可以解析 application/json 类型的请求参数 */
app.use(express.json());
/* 2. 告诉 express 可以解析 application/x-www-form-urlencoded 类型的请求参数 */
app.use(express.urlencoded({extended: false}));
/* 3. 匹配路由,输出 post 请求参数 */
app.post('/api/user/login',  (req, res, next) => {
  console.log(req.body); /* [Object: null prototype] { username: 'ReynMorales', password: '1024' } | { username: 'Reyn Morales', password: 1024 } */
});
 
 
Cookie
添加
示例如下:
// 添加 Cookie
app.get('/api/user/login', (req, res, next) => {
  res.cookie('name', 'reyn', {
    httpOnly: true,
    path: '/',
    maxAge: 60000
  });
  res.end();
});
 
获取
示例如下:
// 1. 下载并导入 cookie-parser
const cookieParser = require('cookie-parser');
// 2. 告诉 Express 利用 cookieParser 解析 Cookie
app.use(cookieParser());
// 3. 通过 req 实例的 cookies 就可以访问被转换为实例的 Cookie
app.get('/api/user/info', (req, res, next) => {
  console.log(req.cookies); // { name: 'reyn' }
});
 
next 方法
实际上,如果利用 Express 相关的实例通过 get 或 post 方法处理请求时,系统将从上至下依次将路由匹配每一个 use、get 和 post,如果匹配成功,那么将停止匹配,且执行相应方法中回调函数的代码,示例如下:
app.use((req, res, next) => {
  console.log('USE: No Route');
});
app.use('/', (req, res, next) => {
  console.log('USE: Root Route');
});
app.get('/',(req, res, next)=>{
  console.log('First GET');
});
app.get('/',(req, res, next)=>{
  console.log('Second GET');
});
app.post('/',(req, res, next)=>{
  console.log('First POST');
});
app.post('/',(req, res, next)=>{
  console.log('Second POST');
});
 
 
- use 方法既可以处理没有路由地址的请求,也可以处理有路由地址的请求
 - use 方法既可以处理 get 请求,也可以处理 post 请求
 - 程序获取客户端请求时,将从上至下依次匹配每一个处理方法,当匹配成功后就停止,并执行相应处理方法中的回调函数
 
如果在每个回调函数中都调用 next 方法,那么输出结果将完全不同,示例如下:
app.use((req, res, next) => {
  console.log('USE: No Route');
  next();
});
app.use('/', (req, res, next) => {
  console.log('USE: Root Route');
  next();
});
app.get('/',(req, res, next)=>{
  console.log('First GET');
  next();
});
app.get('/',(req, res, next)=>{
  console.log('Second GET');
  next();
});
app.post('/',(req, res, next)=>{
  console.log('First POST');
  next();
});
app.post('/',(req, res, next)=>{
  console.log('Second POST');
  next();
});
 
如果通过 get 请求方式利用根路由地址访问,那么输出内容如下:
USE: No Route
USE: Root Route
First GET
Second GET
 
如果通过 post 请求方法利用根路由地址访问,那么输出内容如下:
USE: No Route
USE: Root Route
First POST
Second POST
 
根据上述示例,相关总结如下:
- 在执行完毕相应的回调函数之后,如果调用 next 方法,那么路由匹配将不会停止
 - 在执行完毕相应的回调函数之后,如果没有调用 next 方法,那么路由匹配将停止
 
利用 next 方法可以将同一个业务逻辑的不同步骤放在不同的方法中,以提高代码的可读性和可维护性,示例如下:
/* 判断用户是否登录 */
app.get('/api/user/info', (req, res, next) => {
  if (req.query.isLogin === 'false') {
    res.end('Login Please!');
  } else {
    // 如果用户已经登录,那么移至下一个方法中
    next();
  }
});
/* 将用户信息返回至客户端 */
app.get('/api/user/info', (req, res, next) => {
  res.json({
    username: 'Reyn Morales',
    age: 21,
    gender: 'Male'
  });
});
 
实际上,不论是 use、get 还是 post 方法,都可将编写无限个回调函数,示例如下:
app.get('/api/user/info', (req, res, next) => {
  if (req.query.isLogin === 'false') {
    res.end('Login Please!');
  } else {
    // 如果用户已经登录,那么移至下一个方法中处理剩余的业务逻辑
    next();
  }
}, (req, res, next) => {
  res.json({
    username: 'Reyn Morales', age: 21, gender: 'Male'
  });
});
 
 
错误处理
利用 Express 的路由匹配规则可以在所有请求的最后实现错误处理,示例如下:
// 1. 下载并导入 http-errors
const createError = require('http-errors');
app.post('/api/user/login', (req, res, next) => {
  res.end('Login');
});
app.get('/api/user/info', (req, res, next) => {
  res.end('Info');
});
// 2. 路由地址匹配失败
app.use((req, res, next) => {
  next(createError(404, 'Not Found'));  // 新建一个错误实例
}, (err, req, res, next)=>{
  console.log(err.status, err.message);
  res.end(err.message); // 将错误信息返回至客户端
});
 
 
中间件
在 Express 中,中间件的本质就是一个函数,此函数接收 3 个参数:request 请求实例,response 响应实例,next 函数,每出现一个请求,程序将从上至下依次匹配每一个中间件,如果匹配则执行此中间件中的代码,通过中间件可以将一个请求的所有处理过程分发到若干个环节中,从而提高代码的可读性和可维护性,分类如下:
- 应用 
  
- 绑定到 express 实例上的中间件
 
 - 路由 
  
- 绑定到 router 实例上的中间件
 
 - 错误处理 
  
- 错误处理中间件可以传入 4 个参数:err、req、res、next
 
 - 内置 
  
- express.static()、express.json()、express.urlencoded()、…
 
 - 第三方 
  
- cookie-parser、…
 
 
generate-express
generate-express 是一个 express 项目的脚手架工具,通过此工具可以快速生成一个功能齐全的项目框架,步骤如下:
-  
下载 generate-express
npm install generate-express -g -  
利用 npx 命令创建一个 express 项目
npx generate-express <ProjectName> 
常用模块
- Development 
  
- cross-env 
    
- 环境切换
 
 - nodemon 
    
- 项目启动
 
 
 - cross-env 
    
 - Production 
  
- ajv 
    
- 模板引擎
 
 - redis
 - mysql2
 - sequelize
 - connect-redis 
    
- express 项目中 redis 相关的中间件
 
 - cookie-parser 
    
- express 项目中 cookie 相关的中间件
 
 - express-session 
    
- express 项目中 session 相关的中间件
 
 - http-errors 
    
- express 项目中错误处理相关的中间件
 
 - morgan 
    
- express 项目中日志记录相关的中间件
 
 
 - ajv 
    
 










