JS 流行框架(二):Express
admin
2024-03-04 20:30:14
0

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 实例的 get 方法专门用于响应客户端在某个特定路由下的 get 请求,在匹配到特定的路由之后,将执行相应的回调函数,系统将自动传入请求实例 req、响应实例 res 以及 next 方法,req 和 res 即为 NodeJS 中 http 模块下的相应实例,不过 Express 为它们添加了很多实用的方法,而 next 方法的作用将在之后介绍

网页

静态资源

示例如下:

// 利用 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'});
});

上述示例中,res 实例的 render 方法是 Express 为响应实例添加的方法,默认的 res 并没有此方法,通过 render 方法可以非常简单地渲染动态资源,此外,必须注意的是,动态资源的扩展名必须为模板引擎的名称,例如示例中通过 ejs 模板引擎渲染动态资源,那么动态资源的名称即为 login.ejs

路由

非模块化

通常情况下,通过 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' } */
});

上述示例中,通过 req 实例的 query 属性可以直接获取被转换为实例的 get 请求参数

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 } */
});

上述示例中,通过 req 实例的 body 属性可以直接获取被转换为实例的 post 请求参数

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');
});

上述示例中,如果在浏览器中利用根路由访问此程序(不论是通过 get 请求方式,还是 post 请求方式),那么将输出 USE: No Route,如果将第一个和第二个 use 方法的位置互换,那么将输出 USE: Root Route,总结如下:

  1. use 方法既可以处理没有路由地址的请求,也可以处理有路由地址的请求
  2. use 方法既可以处理 get 请求,也可以处理 post 请求
  3. 程序获取客户端请求时,将从上至下依次匹配每一个处理方法,当匹配成功后就停止,并执行相应处理方法中的回调函数

如果在每个回调函数中都调用 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

根据上述示例,相关总结如下:

  1. 在执行完毕相应的回调函数之后,如果调用 next 方法,那么路由匹配将不会停止
  2. 在执行完毕相应的回调函数之后,如果没有调用 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'});
});

上述示例实现的功能和上一个示例完全相同,区别是将不同请求的回调函数放在了同一个请求中(路由地址都是相同的),如此的方式同样符合路由匹配规则,如果在请求的第一个回调函数中没有调用 next 方法,那么此请求的剩余回调函数将都不被执行

错误处理

利用 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); // 将错误信息返回至客户端
});

上述示例中,由于每个请求方法中都没有调用 next 方法,所以如果请求路由匹配那么将不会出现错误,如果路由地址都不匹配,由于 use 可以匹配任何路由地址,所以将执行 use 方法中的回调函数,从而实现错误处理

中间件

在 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 项目的脚手架工具,通过此工具可以快速生成一个功能齐全的项目框架,步骤如下:

  1. 下载 generate-express

    npm install generate-express -g
    
  2. 利用 npx 命令创建一个 express 项目

    npx generate-express 
    

常用模块

  • Development
    • cross-env
      • 环境切换
    • nodemon
      • 项目启动
  • Production
    • ajv
      • 模板引擎
    • redis
    • mysql2
    • sequelize
    • connect-redis
      • express 项目中 redis 相关的中间件
    • cookie-parser
      • express 项目中 cookie 相关的中间件
    • express-session
      • express 项目中 session 相关的中间件
    • http-errors
      • express 项目中错误处理相关的中间件
    • morgan
      • express 项目中日志记录相关的中间件

相关内容

热门资讯

伊朗警告:若发电站遭袭,将摧毁... 针对“特朗普要求伊朗在48小时内开放霍尔木兹海峡,否则将打击伊朗‘各类发电厂’并将其摧毁”一事,当地...
特朗普:美国已将伊朗从地图上抹... 综合外媒报道,伊朗总统佩泽希齐扬当地时间22日在社交媒体发文说,霍尔木兹海峡对所有人开放,唯独不欢迎...
【韩国检方调查炼油企业是否存在... 【韩国检方调查炼油企业是否存在价格串通行为】韩联社报道称,韩国检方搜查了该国四大炼油企业的办公室,以...
伊朗总统回应特朗普:企图将伊朗... 当地时间3月22日傍晚,伊朗总统佩泽希齐扬在社交媒体发文表示,“企图‘将伊朗从地图上抹去’,是对一个...
山贼来了?伊朗设卡收费,向运行... 最近,中东的乱局又添了一个让人哭笑不得的新剧情。美国和以色列对伊朗的军事打击已经持续了快一个月,就在...
个贷有新规,借钱注意啥 个贷新... 问:近日,《个人贷款业务明示综合融资成本规定》发布,要求贷款人开展个人贷款业务时,向借款人展示综合融...
1分钱一度电,分布式光伏彻底“... “我的天呀,1月份我的光伏电费账单,上网电价竟然只有1分钱一度!”华夏能源网注意到,1月份,经济、用...
猪肉价格“大跳水”,但钱越来越... 总第4540期作者 |餐饮老板内参内参君猪肉暴跌,离历史大底只差3毛钱万物皆涨的年代,终于有食材价格...
高盛上调3-4月布油均价至11... 财联社3月23日讯(编辑 黄君芝)随着美伊战争僵持不下,高盛在不到两周的时间里第二次上调了油价预期,...
增资90亿,业绩大涨,国寿财险... 国寿财险的注册资本金增加至278亿元,一举跃居行业首位。文/每日财报 栗佳近期,随着一笔高达90亿...