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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 重学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);
}
};



相关文章
|
14天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端框架
【10月更文挑战第34天】在数字化时代,后端开发如同一座桥梁,连接着用户界面与数据处理的两端。本文将通过Node.js这一轻量级、高效的平台,带领读者领略后端框架的魅力。我们将从基础概念出发,逐步深入到实战应用,最后探讨如何通过代码示例来巩固学习成果,使读者能够在理论与实践之间架起自己的桥梁。
|
2天前
|
缓存 监控 JavaScript
Vue.js 框架下的性能优化策略与实践
Vue.js 框架下的性能优化策略与实践
|
15天前
|
Web App开发 JavaScript 前端开发
探索后端开发:Node.js与Express的完美结合
【10月更文挑战第33天】本文将带领读者深入了解Node.js和Express的强强联手,通过实际案例揭示它们如何简化后端开发流程,提升应用性能。我们将一起探索这两个技术的核心概念、优势以及它们如何共同作用于现代Web开发中。准备好,让我们一起开启这场技术之旅!
31 0
|
15天前
|
Web App开发 JavaScript 前端开发
构建高效后端服务:Node.js与Express框架的实践
【10月更文挑战第33天】在数字化时代的浪潮中,后端服务的效率和可靠性成为企业竞争的关键。本文将深入探讨如何利用Node.js和Express框架构建高效且易于维护的后端服务。通过实践案例和代码示例,我们将揭示这一组合如何简化开发流程、优化性能,并提升用户体验。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
1月前
|
JavaScript 前端开发 中间件
探索后端技术:Node.js与Express框架的完美融合
【10月更文挑战第7天】 在当今数字化时代,Web应用已成为日常生活不可或缺的一部分。本文将深入探讨后端技术的两大重要角色——Node.js和Express框架,分析它们如何通过其独特的特性和优势,为现代Web开发提供强大支持。我们将从Node.js的非阻塞I/O和事件驱动机制,到Express框架的简洁路由和中间件特性,全面解析它们的工作原理及应用场景。此外,本文还将分享一些实际开发中的小技巧,帮助你更有效地利用这些技术构建高效、可扩展的Web应用。无论你是刚入门的新手,还是经验丰富的开发者,相信这篇文章都能为你带来新的启发和思考。
|
1月前
|
开发框架 JavaScript 前端开发
使用 Node.js 和 Express 构建 Web 应用
【10月更文挑战第2天】使用 Node.js 和 Express 构建 Web 应用
|
20天前
|
JavaScript 中间件 关系型数据库
构建高效的后端服务:Node.js 与 Express 的实践指南
在后端开发领域,Node.js 与 Express 的组合因其轻量级和高效性而广受欢迎。本文将深入探讨如何利用这一组合构建高性能的后端服务。我们将从 Node.js 的事件驱动和非阻塞 I/O 模型出发,解释其如何优化网络请求处理。接着,通过 Express 框架的简洁 API,展示如何快速搭建 RESTful API。文章还将涉及中间件的使用,以及如何结合 MySQL 数据库进行数据操作。最后,我们将讨论性能优化技巧,包括异步编程模式和缓存策略,以确保服务的稳定性和扩展性。
|
27天前
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的完美结合
【10月更文挑战第21天】本文将引导你走进Node.js和Express框架的世界,探索它们如何共同打造一个高效、可扩展的后端服务。通过深入浅出的解释和实际代码示例,我们将一起理解这一组合的魅力所在,并学习如何利用它们来构建现代Web应用。
46 1
|
17天前
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的融合之道
【10月更文挑战第31天】在追求快速、灵活和高效的后端开发领域,Node.js与Express框架的结合如同咖啡遇见了奶油——完美融合。本文将带你探索这一组合如何让后端服务搭建变得既轻松又充满乐趣,同时确保你的应用能够以光速运行。
24 0
|
1月前
|
JSON JavaScript 前端开发
Node.js Express 框架
10月更文挑战第7天
30 2