​Node.js系列十 - express开发web服务器

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: express开发web服务器

一. Express初体验


1.1. 认识Web框架


前面我们已经学习了使用http内置模块来搭建Web服务器,为什么还要使用框架?

  • 原生http在进行很多处理时,会较为复杂;
  • 有URL判断、Method判断、参数处理、逻辑代码处理等,都需要我们自己来处理和封装;
  • 并且所有的内容都放在一起,会非常的混乱;

目前在Node中比较流行的Web服务器框架是express、koa;

  • 我们先来学习express,后面再学习koa,并且对他们进行对比;

express早于koa出现,并且在Node社区中迅速流行起来:

  • 我们可以基于express快速、方便的开发自己的Web服务器;
  • 并且可以通过一些实用工具和中间件来扩展自己功能;


1.2. express的安装


express的使用过程有两种方式:

  • 方式一:通过express提供的脚手架,直接创建一个应用的骨架;
  • 方式二:从零搭建自己的express应用结构;

方式一:安装express-generator

npm install -g express-generator

创建项目:

express express-demo

项目目录如下:

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

我们可以安装依赖,将程序跑起来:

npm install
node bin/www

方式二:从零学习搭建

刚才创建的项目express项目,很多内容可能我们并不认识,所以刚开始我们最好从零来学习。

初始化一个新的项目

npm init -y

express的安装:

  • 目前最新的 release 版本是4.17.1,我们使用该版本;
npm install express


1.3. express初体验


我们来创建自己的第一个express程序:

const express = require('express');
// 创建服务器
const app = express();
// /home的get请求处理
app.get("/home", (req, res) => {
  res.end("Hello Home");
});
// /login的post请求处理
app.post("/login", (req, res) => {
  res.end("Hello Login");
});
// 开启监听
app.listen(8000, () => {
  console.log("服务器启动成功~");
})

我们会发现,之后的开发过程中,可以方便的将请求进行分离:

  • 无论是不同的URL,还是get、post等请求方式;
  • 这样的方式非常方便我们已经进行维护、扩展;

当然,这只是初体验,接下来我们来探索更多的用法;


1.4. 请求和响应


请求的路径中如果有一些参数,可以这样表达:

  • /users/:userId
  • request对象中要获取可以通过 req.params.userId;

返回数据,我们可以方便的使用json:

const express = require('express');
const app = express();
app.get('/users/:userId', (req, res, next) => {
  console.log(req.params.userId);
  res.json({username: "coderwhy", level: 99});
});
app.listen(8000, () => {
  console.log("静态服务器启动成功~");
})


二. Express中间件


2.1. 认识中间件


Express是一个路由和中间件的Web框架,它本身的功能非常少:

  • Express应用程序本质上是一系列中间件函数的调用;

中间件是什么呢?

  • 中间件的本质就是一个回调函数;
  • 这个回调函数接受三个参数:
  • 请求对象(request对象);
  • 响应对象(response对象);
  • next函数(在express中定义的用于执行下一个中间件的函数);

中间件中可以执行哪些任务呢?

  • 执行任何代码;
  • 更改请求(request)和响应(response)对象;
  • 结束请求-响应周期(返回数据);
  • 调用栈中的下一个中间件;

如果当前中间件功能没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件功能,否则,请求将被挂起。

中间件函数调用的元素:

image.png


2.2. 应用中间件


那么,如何将一个中间件应用到我们的应用程序中呢?

  • express主要提供了两种方式:app/router.useapp/router.methods
  • 可以是 app,也可以是router,router我们后续再学习:
  • methods指的是常用的请求方式,比如:app.get或app.post等;

我们先来学习use的用法,因为methods的方式本质是use的特殊情况;

案例一:最普通的中间件

之所以称之为最普通的中间件,是因为无论是什么path、methods都会应用该中间件;

const express = require('express');
const app = express();
app.use((req, res, next) => {
  console.log("common middleware 01");
  next();
})
app.use((req, res, next) => {
  console.log("common middleware 02");
  res.end("Hello Common Middleware~");
})
app.listen(8000, () => {
  console.log("中间件服务器启动成功~");
})

中间件的执行顺序:

  • 在匹配上的情况下,中间件按照注册的顺序执行;

案例二:path匹配中间件

如果我们希望匹配一个明确的路径,也可以使用use方法:

// 案例二: 路径匹配中间件
app.use('/home', (req, res, next) => {
  console.log("home middleware 01");
  next();
});
app.use('/home', (req, res, next) => {
  console.log("home middleware 02");
  next();
  res.end("Hello Home middleware");
});
app.use((req, res, next) => {
  console.log("common middleware");
});

案例三:path和method匹配中间件

// 案例三: method匹配中间件
app.get('/home', (req, res, next) => {
  console.log("home get middleware");
  next();
})
app.post('/login', (req, res, next) => {
  console.log("login post middleware");
  next();
});
app.use((req, res, next) => {
  console.log("common middleware");
});

案例四:注册多个中间件

// 案例四: 注册多个中间件
const homeMiddleware1 = (req, res, next) => {
  console.log('home middleware 01');
  next();
}
const homeMiddleware2 = (req, res, next) => {
  console.log('home middleware 02');
  next();
}
const homeHandle = (req, res, next) => {
  res.end("Hello Home~");
}
app.get('/home', homeMiddleware1, homeMiddleware2, homeHandle);


2.3. 应用其他中间件


并非所有的中间件都需要我们从零去编写:

  • express有内置一些帮助我们完成对request解析的中间件;
  • registry仓库中也有很多可以辅助我们开发的中间件;


2.3.1. request解析中间件


在客户端发送post请求时,会将数据放到body中:

  • 客户端可以通过json的方式传递;
  • 也可以通过form表单的方式传递;

我们这里先使用json传递给服务器body:

image.png                                                      json传递body

不进行解析时的操作:

app.post('/login', (req, res, next) => {
  req.on('data', (data) => {
    console.log(data.toString());
  })
  req.on('end', () => {
    res.end("登录成功~");
  });
});

我们也可以自己编写中间件来解析JSON:

app.use((req, res, next) => {
  if (req.headers['content-type'] === 'application/json') {
    req.on('data', (data) => {
      const userInfo = JSON.parse(data.toString());
      req.body = userInfo;
    })
    req.on('end', () => {
      next();
    })
  } else {
    next();
  }
})
app.post('/login', (req, res, next) => {
  console.log(req.body);
  res.end("登录成功~");
});

但是,事实上我们可以使用expres内置的中间件或者使用body-parser来完成:

app.use(express.json());
app.post('/login', (req, res, next) => {
  console.log(req.body);
  res.end("登录成功~");
});

如果我们解析的是 application/x-www-form-urlencoded

image.png                                                     form传递body

我们可以使用express自带的 urlencoded函数来作为中间件:

  • 传入的extended用于表示使用哪一种解析方式:
  • true:使用qs第三方模块;
  • false:使用querystring内置模块;
  • 备注:它们之间的区别这里不展开讲解;
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.post('/login', (req, res, next) => {
  console.log(req.body);
  res.end("登录成功~");
});


2.3.2. 日志记录中间件


如果我们希望将请求日志记录下来,那么可以使用express官网开发的第三方库:morgan

安装morgan:

npm install morgan

直接作为中间件使用即可:

const loggerWriter = fs.createWriteStream('./log/access.log', {
  flags: 'a+'
})
app.use(morgan('combined', {stream: loggerWriter}));


2.3.3. 上传文件中间件


图片上传我们可以使用express官方开发的第三方库:multer

npm install multer

上传文件,并且默认文件名:

const upload = multer({
  dest: "uploads/"
})
app.post('/upload', upload.single('file'), (req, res, next) => {
  console.log(req.file.buffer);
  res.end("文件上传成功~");
})

添加文件名后缀:

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
})
app.post('/upload', upload.single('file'), (req, res, next) => {
  console.log(req.file.buffer);
  res.end("文件上传成功~");
})

我们也可以上传多张图片:

app.use('/upload', upload.array('files'), (req, res, next) => {
  console.log(req.files);
});

如果我们希望借助于multer帮助我们解析一些form-data中的普通数据,那么我们可以使用any:

  • 请求如下:

image.png

app.use(upload.any());
app.use('/login', (req, res, next) => {
  console.log(req.body);
});


2.4. 请求和响应


客户端传递到服务器参数的方法常见的是5种:

  • 方式一:通过get请求中的URL的params;
  • 方式二:通过get请求中的URL的query;
  • 方式三:通过post请求中的body的json格式(中间件中已经使用过);
  • 方式四:通过post请求中的body的x-www-form-urlencoded格式(中间件使用过);
  • 方式五:通过post请求中的form-data格式(中间件中使用过);


2.4.1. 请求解析


方式一:params

请求地址:http://localhost:8000/login/abc/why

获取参数:

app.use('/login/:id/:name', (req, res, next) => {
  console.log(req.params);
  res.json("请求成功~");
})

方式二:query

请求地址:http://localhost:8000/login?username=why&password=123

获取参数:

app.use('/login', (req, res, next) => {
  console.log(req.query);
  res.json("请求成功~");
})


2.4.2. 响应方式


end方法

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

res.end("Hello World");

json方法

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

res.json({name: "why", age: 18});

status方法

用于设置状态码:

res.status(204);


三. 其他支持补充


3.1. 路由的使用


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

  • 一方面完整的Web服务器包含非常多的处理逻辑;
  • 另一方面有些处理逻辑其实是一个整体,我们应该将它们放在一起:比如对users相关的处理
  • 获取用户列表;
  • 获取某一个用户信息;
  • 创建一个新的用户;
  • 删除一个用户;
  • 更新一个用户;

我们可以使用 express.Router来创建一个路由处理程序:

  • 一个Router实例拥有完整的中间件和路由系统;
  • 因此,它也被称为 迷你应用程序(mini-app);
// 用户相关的处理
const userRouter = express.Router();
userRouter.get('/', (req, res, next) => {
  res.end("用户列表");
});
userRouter.post('/', (req, res, next) => {
  res.end("创建用户");
});
userRouter.delete('/', (req, res, next) => {
  res.end("删除用户");
});
app.use('/users', userRouter);

当然,我们可以配置更多的路由,并且将所有的逻辑放到一个单独的文件中。


3.2. 静态资源服务器


部署静态资源我们可以选择很多方式:

  • Node也可以作为静态资源服务器,并且express给我们提供了方便部署静态资源的方法;
const express = require('express');
const app = express();
app.use(express.static('./build'));
app.listen(8000, () => {
  console.log("静态服务器启动成功~");
})


3.3. 错误处理方式


app.use((req, res, next) => {
  next(new Error("USER DOES NOT EXISTS"));
});
app.use((err, req, res, next) => {
  const message = err.message;
  switch (message) {
    case "USER DOES NOT EXISTS":
      res.status(400).json({message})
  }
  res.status(500)
})


3.4. 源码分析


3.4.1. 创建app的过程


express函数的本质其实是createApplication

image.png

当我们调用app.listen的时候,本质上是调用proto中的listen

  • 因为上面有进行 mixin 的操作;

image.png


3.4.2. 注册中间件


比如我们通过use来注册一个中间件,源码中发生了什么?

  • 我们会发现无论是app.use还是app.methods都会注册一个主路由;
  • 我们会发现app本质上会将所有的函数,交给这个主路由去处理的;

image.png

我们来看一下router.use中又做了什么事情?

  • 本质上一个函数会创建一个layer,并且会被放入到stack中;

image.png


3.4.3. 请求的处理过程


如果有一个请求过来,那么从哪里开始呢?

  • app函数被调用开始的;

image.png

app.handle本质上会去调用router.handle:

image.png

router.handle中做的什么事情呢?

image.png

image.png

相关文章
|
1月前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
144 45
|
13天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
28 2
|
14天前
|
缓存 负载均衡 JavaScript
构建高效后端服务:Node.js与Express框架实践
在数字化时代的浪潮中,后端服务的重要性不言而喻。本文将通过深入浅出的方式介绍如何利用Node.js及其强大的Express框架来搭建一个高效的后端服务。我们将从零开始,逐步深入,不仅涉及基础的代码编写,更会探讨如何优化性能和处理高并发场景。无论你是后端新手还是希望提高现有技能的开发者,这篇文章都将为你提供宝贵的知识和启示。
|
1月前
|
JavaScript 中间件 关系型数据库
构建高效的后端服务:Node.js 与 Express 的实践指南
在后端开发领域,Node.js 与 Express 的组合因其轻量级和高效性而广受欢迎。本文将深入探讨如何利用这一组合构建高性能的后端服务。我们将从 Node.js 的事件驱动和非阻塞 I/O 模型出发,解释其如何优化网络请求处理。接着,通过 Express 框架的简洁 API,展示如何快速搭建 RESTful API。文章还将涉及中间件的使用,以及如何结合 MySQL 数据库进行数据操作。最后,我们将讨论性能优化技巧,包括异步编程模式和缓存策略,以确保服务的稳定性和扩展性。
|
28天前
|
前端开发 API 开发者
Python Web开发者必看!AJAX、Fetch API实战技巧,让前后端交互如丝般顺滑!
在Web开发中,前后端的高效交互是提升用户体验的关键。本文通过一个基于Flask框架的博客系统实战案例,详细介绍了如何使用AJAX和Fetch API实现不刷新页面查看评论的功能。从后端路由设置到前端请求处理,全面展示了这两种技术的应用技巧,帮助Python Web开发者提升项目质量和开发效率。
42 1
|
1月前
|
XML 安全 PHP
PHP与SOAP Web服务开发:基础与进阶教程
本文介绍了PHP与SOAP Web服务的基础和进阶知识,涵盖SOAP的基本概念、PHP中的SoapServer和SoapClient类的使用方法,以及服务端和客户端的开发示例。此外,还探讨了安全性、性能优化等高级主题,帮助开发者掌握更高效的Web服务开发技巧。
|
26天前
|
Web App开发 JavaScript 前端开发
探索后端开发:Node.js与Express的完美结合
【10月更文挑战第33天】本文将带领读者深入了解Node.js和Express的强强联手,通过实际案例揭示它们如何简化后端开发流程,提升应用性能。我们将一起探索这两个技术的核心概念、优势以及它们如何共同作用于现代Web开发中。准备好,让我们一起开启这场技术之旅!
39 0
|
26天前
|
Web App开发 JavaScript 前端开发
构建高效后端服务:Node.js与Express框架的实践
【10月更文挑战第33天】在数字化时代的浪潮中,后端服务的效率和可靠性成为企业竞争的关键。本文将深入探讨如何利用Node.js和Express框架构建高效且易于维护的后端服务。通过实践案例和代码示例,我们将揭示这一组合如何简化开发流程、优化性能,并提升用户体验。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
28天前
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的融合之道
【10月更文挑战第31天】在追求快速、灵活和高效的后端开发领域,Node.js与Express框架的结合如同咖啡遇见了奶油——完美融合。本文将带你探索这一组合如何让后端服务搭建变得既轻松又充满乐趣,同时确保你的应用能够以光速运行。
27 0
|
2月前
|
JavaScript 前端开发 中间件
探索后端技术:Node.js与Express框架的完美融合
【10月更文挑战第7天】 在当今数字化时代,Web应用已成为日常生活不可或缺的一部分。本文将深入探讨后端技术的两大重要角色——Node.js和Express框架,分析它们如何通过其独特的特性和优势,为现代Web开发提供强大支持。我们将从Node.js的非阻塞I/O和事件驱动机制,到Express框架的简洁路由和中间件特性,全面解析它们的工作原理及应用场景。此外,本文还将分享一些实际开发中的小技巧,帮助你更有效地利用这些技术构建高效、可扩展的Web应用。无论你是刚入门的新手,还是经验丰富的开发者,相信这篇文章都能为你带来新的启发和思考。