重学Node.js及其框架(Express, Koa, egg.js) 之 Express框架

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 重学Node.js及其框架(Express, Koa, egg.js) 之 Express框架

总结自:Coderwhy老师的nodejs课程。


官网 expressjs.com/zh-cn/4x/ap…


express项目创建有两种方式:


方式一:通过express提供的脚手架,直接创建一个应用的骨架;


// 安装express-generator
    // 安装脚手架
    npm install -g express-generator
    // 创建项目
    express express项目名
    // 安装依赖
    npm install
    // 启动项目
    node bin/www


生成的目录


.
├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.pug
    ├── index.pug
    └── layout.pug


方式二:从零搭建自己的express应用结构;


npm init -y


这里我还是建议第二种方式来配置我们的express。


中间件


其实学习express就是学习中间件。


什么是中间件


中间件的本质是传递给express的一个回调函数;这个回调函数接受三个参数:


  • 请求对象(request对象);


  • 响应对象(response对象);


  • next函数(在express中定义的用于执行下一个中间件的函数);


  • 并且中间件不调用next(),那么它将只会调用第一个匹配的中间件。


中间件可以做哪些事情


  • 执行任何代码;


  • 更改请求(request)和响应(response)对象;


  • 结束请求-响应周期(返回数据);


  • 调用栈中的下一个中间件;


如果当前中间件功能没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件功能,否则,请求将被挂起。如果next方法传递了参数,那么它将不会调用下一个匹配到的中间件,而是直接匹配到传递了接受错误的中间件。如果没有找到接受错误的中间件,那么它将直接终止程序。


中间件这么牛逼,那如何使用它呢?


中间件的使用方式


  • 最普通的中间件 通过app.use((req, res, next) => {})来使用。


app.use((req, res, next) => {
  console.log("注册了第01个普通的中间件~");
  next();
});


  • path匹配中间件 通过app.use(路径, (req, res, next) => {})来使用。


// 路径匹配的中间件
    app.use('/index', (req, res, next) => {
      console.log("index middleware 01");
    });


  • path和method匹配中间件 通过app.[mothod](路径, (req, res, next) => {})来使用。


app.get('/index', (req, res, next) => {
      console.log("index path and method middleware01");
    });
    app.post('/login', (req, res, next) => {
      console.log("login path and method middleware01");
    })


  • 注册多个中间件 每个注册中间件的方式都可以注册多个中间件。


app.get("/index", (req, res, next) => {
      console.log("index path and method middleware 02");
      next();
    }, (req, res, next) => {
      console.log("index path and method middleware 03");
      next();
    }, (req, res, next) => {
      console.log("index path and method middleware 04");
      res.end("index page");
    });


不管中间件是什么类型的,他都是从前往后匹配的,没有调用next方法,即使匹配到后面的中间件也不会执行。如果一个匹配到的中间件结束了响应,调用了next他会继续执行下一个中间件,没有调用next他将结束响应。


特别需要注意的是,当多个匹配的中间件同时设置了响应和调用了next,该请求响应的结果为最先发出的响应。并且终端会报错。一般我们也不会这样干,只会在最后一个被匹配到的中间件设置响应。


app.use((req, res, next) => {
      console.log("common middleware01");
      next();
    })
    // 路径匹配的中间件
    app.use('/home', (req, res, next) => {
      console.log("home middleware 01");
      next()
      res.send("=====")
    });
    // 中间插入了一个普通的中间件
    app.use((req, res, next) => {
      console.log("common middleware02");
      res.send("+++++")
      next();
    })
    app.use('/home', (req, res, next) => {
      console.log("home middleware 02");
      res.send("----------")
    });


上面事例,最终响应的结果是+++++。


网络异常,图片无法展示
|


解析请求传递的参数


解析params参数


我们直接通过req.params就可以获取到。


app.get('/index/:id/:name', (req, res, next) => {
      console.log(req.params);
      res.end("参数获取成功~");
    })


解析query参数


我们直接通过req.query就可以获取到。


app.get('/login', (req, res, next) => {
      console.log(req.query);
      res.end("用户登录成功~");
    })


解析请求中的json格式数据


调用express中内置的方法express.json()。该方法将返回一个函数。并将解析后的参数放在req.body上。


app.use(express.json())
    app.post("/login", (req, res, next) => {
      console.log("login-body", req.body)
      // 注意end方法只能返回字符串类型或者是buffer类型
      res.end(JSON.stringify(req.body))
      next()
    })


解析请求中的x-www-from-urlencoded格式数据


调用express中内置的方法express.urlencoded({ extended: true })。该方法将返回一个函数。并将解析后的参数放在req.body


  • 如果extended为true,表示它将采取第三方的qs库解析请求的body


  • 如果extended是false,表示他将采取node的querystring模块解析请求的body


app.use(express.urlencoded({
      extended: true
    }))
    app.post("/register", (req, res, next) => {
      console.log("register-body", req.body)
      res.end(JSON.stringify(req.body))
      next()
    })


解析请求中的form-data格式数据


这个express没有提供内置的中间件。我们需要安装multer库来帮助我们解析。不要将它作为全局中间件来使用,上传文件和费文件的form-data格式的数据,可能会产生冲突。


github.com/expressjs/m…


解析非文件的表单数据


const multer = require("multer");
    const upload = multer();
    // 将它作为全局中间件时,它将会产生冲突,不能正确的解析文件上传。所以不要将他作为全局中间件使用
    // app.use(upload.any())// 然后他就会将form表单非文件的数据解析成对象,绑定到req.body上
    app.post('/login', upload.any(), (req, res, next) => {
      console.log("login", req.body);
      res.end("用户登录成功~")
    });


解析文件的表单数据


我们可以指定上传的文件名和后缀。也可以让系统自动分配文件名。


  • any方法不能作为全局的中间件,他会和single,array等方法冲突。


  • array方法第二个参数可以指定上传文件的最大数量。


  • fields方法表示传递不同字段的多文件。


[
      { name: 'avatar', maxCount: 1 },
      { name: 'gallery', maxCount: 8 }
    ]


// 方式一
    // 自定义上传文件的文件名和格式
    const storage = multer.diskStorage({
      destination: (req, file, cb) => {
        cb(null, './uploads/');
      },
      filename: (req, file, cb) => {
        cb(null, Date.now() + path.extname(file.originalname));
      }
    })
    const upload = multer({
      storage
    });
    // 方式二
    // 只提供文件上传的输出位置,文件名随机。
    const upload = multer({
       dest: './uploads/'
    });
    // 上传多个文件。注意文件上传的name都必须为file
    app.post('/uploadmore', upload.array('file'), (req, res, next) => {
      console.log(req.files);
      res.end("文件上传成功~");
    });
    // 上传单个文件。注意文件上传的name都必须为file
    app.post('/uploadsingle', upload.single('file'), (req, res, next) => {
      console.log(req.file);
      res.end("文件上传成功~");
    });
    // 上传多个文件。并且可以指定不同的字段名。
    app.post('/upload', upload.fields([
      {
      name: "file"
    },{
      name: "avatar"
    }
    ]), (req, res, next) => {
      console.log(req.files);
      res.end("文件上传成功~");
    });


网络异常,图片无法展示
|


网络异常,图片无法展示
|


网络异常,图片无法展示
|


响应请求


详细请求,请访问 expressjs.com/zh-cn/api.h…


  • end方法。类似于http中的response.end方法,用法是一致的。


res.end(返回的数据); // 只能是字符串或者buffr类型


  • json方法。json方法中可以传入很多的类型:object、array、string、boolean、number、null等,它们会被转换成json格式返回;


res.json(返回的数据)


  • status方法。用于设置状态码:


res.status(200);


  • get方法。获取请求头中指定的字段


res.get("Content-Type");


  • type方法。用来设置返回值mime类型的。


res.type("application/json");


express路由


为什么要使用路由?


如果我们将所有的代码逻辑都写在app中,那么app会变得越来越复杂:


  • 一方面完整的Web服务器包含非常多的处理逻辑;


  • 另一方面有些处理逻辑其实是一个整体,我们应该将它们放在一起:比如对users相关的处理


  • 获取用户列表;get


  • 获取某一个用户信息;get/:id


  • 创建一个新的用户;post (body携带参数)


  • 删除一个用户;delete/:id


  • 更新一个用户;patch/:id


创建路由


通过express.Router()来创建一个路由实例。


// userRouter.js。用户相关的路由
   const express = require('express');
   const userRouter = express.Router();
   userRouter.get('/', (req, res, next) => {
     res.json(["zh", "llm"]);
   });
   userRouter.get('/:id', (req, res, next) => {
     res.json(`${req.params.id}用户的信息`);
   });
   userRouter.post('/', (req, res, next) => {
     res.json("create user success~");
   });
   module.exports = userRouter;


// index.js
   const express = require('express');
   const userRouter = require('./router/users.js');
   const app = express();
   // 使用路由,并且统一路由路径。
   app.use("/users", userRouter);
   app.listen(8000, () => {
     console.log("路由服务器启动成功~");
   });


部署静态资源


Node也可以作为静态资源服务器,并且express给我们提供了方便部署静态资源的方法;


通过express.static()返回一个中间件。


  • express.static 函数提供的路径相对于您在其中启动 node 进程的目录。如果从另一个目录运行 Express 应用程序,那么对于提供资源的目录使用绝对路径会更安全。


  • 可以为静态文件创建虚拟路径。路径并不实际存在于文件系统中。


app.use("/static", express.static(path.resolve(__dirname, 'uploads')));


上面表示可以访问具有 /static 路径前缀的 uploads 目录中的文件。访问的路径不需要带uploads路径,直接写uploads文件夹下的文件和文件夹即可。


const express = require('express');
    const path = require("path")
    const app = express();
    // 部署静态文件,我们就可以访问项目下的static中的任何文件了
    app.use(express.static(path.resolve(__dirname, "static")));
    app.listen(8000, () => {
      console.log("路由服务器启动成功~");
    });


错误处理


错误处理中间件始终采用四个自变量。必须提供四个自变量,以将函数标识为错误处理中间件函数。即使无需使用 next 对象,也必须指定该对象以保持特征符的有效性。否则,next 对象将被解释为常规中间件,从而无法处理错误。


调用next(new Error("传递错误信息"))如果next中传入参数,那么将是错误参数,通过app.use((err, req, res, next) => {})来对错误做统一处理。 我们会将错误信息定义成常量,然后做处理。


app.use((err, req, res, next) => {
      let status = 400;
      let message = "";
      switch(err.message) {
        case 错误信息对应的常量:
          message = "错误信息对应的常量";
          break;
        case 错误信息对应的常量:
          message = "错误信息对应的常量"
          break;
        default: 
          message = "NOT FOUND~"
      }
      res.status(status);
      res.json({
        errCode: status,
        errMessage: message
      })
    })


需要注意的是,这个中间件主要是为了处理错误,所以需要获取到错误信息,所以应该充当最后一个中间件函数。


express源码分析


网络异常,图片无法展示
|


调用express(),他其实就是调用createApplication(),这个函数返回一个app。并且这个app就是一个中间件调用函数。


var app = function(req, res, next) {
 app.handle(req, res, next);
};


app.listen()函数调用了一下原生的createServer().listen()方法,并传入参数。


app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};


调用app.use()。通过判断,传入的中间件是否为一个函数,如果是,则会将所有中间件传入到一个fns数组中。不是则将报错。


var fns = flatten(slice.call(arguments, offset));
if (fns.length === 0) {
 throw new TypeError('app.use() requires a middleware function')
}


然后遍历这个fns数组,调用router.use()。然后再其中创建一个layer对象,并且将fn传递进去。然后将layer对象加入到一个stack数组中。


var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: false,
end: false
}, fn);
layer.route = undefined;
this.stack.push(layer);


当用户发送请求,我们可以通过app.listen()方法可以看出底层调用app.handle(),其中又调用router.handle()。其中内部不断判断匹配到的中间件函数,然后调用layer.handle_request。


if (route) {
return layer.handle_request(req, res, next);
}


然后内部调用中间件函数。并且传入req, res, next。然后在router.handle()中调用next方法。继续判断下一个中间件。


Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
 // not a standard request handler
 return next();
}
try {
 fn(req, res, next);
} catch (err) {
 next(err);
}
};



相关文章
|
1月前
|
存储 缓存 JavaScript
node 框架
Node 框架以其独特的优势和广泛的应用场景,成为了现代 Web 开发中不可或缺的一部分。掌握 Node 框架的开发技能,对于开发者来说具有重要的意义。
104 59
|
1月前
|
Web App开发 缓存 监控
如何解决Node框架中内存管理的挑战?
解决 Node 框架中内存管理的挑战需要综合运用多种方法,并且需要在开发过程中保持谨慎和细心,不断优化和改进代码。同时,定期进行内存管理的检查和维护也是非常重要的。
114 63
|
1月前
|
安全 前端开发 JavaScript
Node框架的优缺点
Node 框架的优点使其在现代 Web 开发中具有重要地位,但同时也需要开发者在使用过程中注意其缺点,合理选择和应用,以充分发挥其优势,避免潜在问题的出现。随着技术的不断发展和完善,Node 框架也在不断改进和优化,以更好地适应各种应用需求。
90 47
|
1月前
|
缓存 负载均衡 JavaScript
构建高效后端服务:Node.js与Express框架实践
在数字化时代的浪潮中,后端服务的重要性不言而喻。本文将通过深入浅出的方式介绍如何利用Node.js及其强大的Express框架来搭建一个高效的后端服务。我们将从零开始,逐步深入,不仅涉及基础的代码编写,更会探讨如何优化性能和处理高并发场景。无论你是后端新手还是希望提高现有技能的开发者,这篇文章都将为你提供宝贵的知识和启示。
|
1月前
|
存储 JavaScript 搜索推荐
Node框架的安装和配置方法
安装 Node 框架是进行 Node 开发的第一步,通过正确的安装和配置,可以为后续的开发工作提供良好的基础。在安装过程中,需要仔细阅读相关文档和提示,遇到问题及时解决,以确保安装顺利完成。
100 2
|
1月前
|
Web App开发 JSON JavaScript
Node.js 中的中间件机制与 Express 应用
Node.js 中的中间件机制与 Express 应用
|
安全 JavaScript 前端开发
Javascript框架库漏洞验证
Javascript框架库漏洞验证
2952 0
Javascript框架库漏洞验证
|
3月前
|
JavaScript 前端开发
常用的 JavaScript 框架和库
常用的 JavaScript 框架和库
161 7