Express(一) ——简单入门

背景:参加的青训营项目,使用Express来实现后端,个人被分配到后端去。于是,简单速通了下Express。项目结束,回头写下笔记,沉淀一下。

个人博客:Express(一) ——简单入门

Express是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。

开始前可以先安装Postman,很好用的接口测试工具

1. Hello World

首先,安装express到项目中npm i express

然后,开始代码世界。

// 1. 加载express
const express = require('express');// 2. 调用express()获取。express()函数是express模块​​导出的顶级函数
const app = express();// 3. 设置请求对应的处理函数。下面的例子中,当客户端以GET方法请求/时就会调用处理函数
app.get('/', (req, res) => {res.send('Hello World!');
})// 4. 启动web服务
app.listen(8080, () => {console.log('http://localhost:8080/');
})

最后,命令行执行nodemon app.jsnode app.jsnodemon支持热更新。

2. 路由

路由是指服务器端应用程序如何响应特定端点的客户端请求。由一个URI(路径标识)和一个特定的HTTP方法(GET、POST等)组成的。

路由的定义结构:

app.METHOD(PATH, HANDLER);
  • app:express实例
  • METHOD:是一个HTTP请求方法
  • PATH:服务端路径
  • HANDLER:当路由匹配到时执行的处理函数。参数:requestresponse对象分别处理请求和响应数据
const express = require('express');const app = express();app.get('/', (req, res) => {  // GET请求res.send('Hello World!');
})app.post('/', (req, res) => {   // POST请求res.send('post /');
})app.put('/user', (req, res) => {    // PUT请求res.send('put user');
})app.delete('/user', (req, res) => {  // DELETE请求res.send('delete user');
})app.listen(8080, () => {console.log('http://localhost:8080/');
})

2.1 请求对象

req对象代表HTTP请求。

const express = require('express');const app = express();app.get('/', (req, res) => {console.log("请求地址: ", req.url);console.log("请求方法: ", req.method);console.log("请求头: ", req.headers);console.log("请求参数: ", req.query);res.end();  // 结束响应。没有的话,客户端会一直等待回应
})app.listen(8080, () => {console.log('http://localhost:8080/');
})

postman测试用:http://localhost:8080/?name=clz

2.2 响应对象

res对象表示收到HTTP请求后发送的HTTP响应。

2.2.1 状态码及状态信息

const express = require('express');const app = express();app.get('/', (req, res) => {res.statusCode = 404;   // 设置响应状态码res.statusMessage = "test";    // 设置响应状态信息。这里是测试,理论上来说404应该对应Not Found,这样子才有意义res.end();  // 结束响应
})app.listen(8080, () => {console.log('http://localhost:8080/');
})

2.2.2 发送多段文本

const express = require('express');const app = express();app.get('/', (req, res) => {res.write('hello ');res.write('world');   // res.write()只是发送数据,还是需要res.end()来结束响应// res.end('hello world');   // 结束相应的同时发送数据res.end();  // 结束响应
})app.listen(8080, () => {console.log('http://localhost:8080/');
})

2.2.3 cookie

const express = require('express');const app = express();app.get('/', (req, res) => {res.cookie('name', 'clz');  // 设置cookieres.end();  // 结束响应
})app.listen(8080, () => {console.log('http://localhost:8080/');
})

2.3 路由路径

可以使用正则表达式语法

// 匹配根路径
app.get("/", function(req, res) {res.send("root");
});// 匹配/abc
app.get("/abc", function(req, res) {res.send("abc");
});// 匹配/test.text
app.get("/test.text", function(req, res) {res.send("test.text");
});// 匹配/acd、/abcd
app.get("/ab?cd", function(req, res) {res.send("ab?cd");
});// 匹配/abcd、/abxxxxcd
app.get("/ab*cd", function(req, res) {res.send("ab?cd");
});// 匹配/abe、/abcde
app.get("/ab(cd)?e", function(req, res) {res.send("ab?cd");
});// 匹配所有包含a
app.get(/a/, function(req, res) {res.send("/a/");
});// 匹配以fly结尾的,包括/test.fly,/test/aaa/fly等
app.get(/.*fly$/, function(req, res) {res.send("/.*fly$/");
});

2.4 动态路径

app.get("/users/:userId/books/:bookId", function(req, res) {res.send(req.params);
});// 限制动态参数
app.get("/:a(\\d+)", function (req, res) {   res.send(req.params);
});

3. 案例

创建一个简单的CRUD接口服务。增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)

  • 查询任务列表:GET /todos
  • 根据ID查询单个任务:GET /todos/:id
  • 添加任务:POST /todos
  • 修改任务:PATCH /todos
  • 删除任务:DELETE /todos/:id

3.1 路由设计

const express = require('express');const app = express();app.get('/todos', (req, res) => {res.send('查询任务列表');
})app.get('/todos/:id', (req, res) => {res.send(`根据ID查询单个任务, id是${req.params.id}`);   // 通过req.params.id来获取动态的路径参数id
})app.post('/todos', (req, res) => {res.send('添加任务');
})app.patch('/todos/:id', (req, res) => {res.send(`修改任务, id是${req.params.id}`);
})app.delete('/todos/:id', (req, res) => {res.send(`删除任务, id是${req.params.id}`);
})app.listen(8080, () => {console.log('http://localhost:8080/');
})

3.2 获取任务列表

数据文件db.json

{"todos": [{"id": 1,"title": "express"},{"id": 2,"title": "笔记"},{"id": 3,"title": "更新博客"}]
}
app.get('/todos', (req, res) => {fs.readFile('./db.json', 'utf8', (err, data) => {if (err) {return res.status(500).json({  // res.json()专门发送json格式的数据,不是json格式会报错error: err.message})}const db = JSON.parse(data);    // 把字符串转成JSON对象res.status(200).json(db.todos);})
})

3.3 根据ID查询单个任务

app.get('/todos/:id', (req, res) => {fs.readFile('./db.json', 'utf8', (err, data) => {if (err) {return res.status(500).json({error: err.message})}const db = JSON.parse(data);const todo = db.todos.find(todo => todo.id === Number.parseInt(req.params.id));   // url中的动态参数是字符串if (!todo) {    // 任务id不存在return res.status(404).end();   // 需要return阻止代码继续往下执行,否则会出现既发送404又发送200}res.status(200).json(todo);})
})

3.4 封装db模块

从上面的代码中可以发现,读取数据文件部分逻辑一样,即可以封装成单独的模块db.js

db.js

const fs = require('fs');
const { promisify } = require('util');  // 把callback形式的异步api转化成promise形式的
const path = require('path');const readFile = promisify(fs.readFile);const dbPath = path.join(__dirname, './db.json');exports.getDb = async () => {const data = await readFile(dbPath, 'utf8');return JSON.parse(data)
}

封装后的app.js(后面的路由没有变化)

const express = require('express');const { getDb } = require('./db.js');const app = express();app.get('/todos', async (req, res) => {try {   // 处理异常的必要性:没有抛出异常的话,可能会一直在等待响应const db = await getDb();   // 因为getDb是async的,所以所有形式都会被封装成Promise,所以获取数据都要awaitres.status(200).json(db.todos);   // // res.json()专门发送json格式的数据,不是json格式会报错} catch (err) {res.status(500).json({error: err.message})}
})app.get('/todos/:id', async (req, res) => {try {const db = await getDb();const todo = db.todos.find(todo => todo.id === Number.parseInt(req.params.id));   // url中的动态参数是字符串if (!todo) {    // 任务id不存在return res.status(404).end();   // 需要return阻止代码继续往下执行,否则会出现既发送404又发送200}res.status(200).json(todo);} catch (err) {res.status(500).json({error: err.message})}
})

3.5 添加任务

app.post('/todos', (req, res) => {// 1. 获取客户端请求体参数console.log(req.body);res.end();
})

然后,会发现很恐怖的事情

那么,这个时候就需要配置表单请求体来解决上述问题

app.use(express.json())   // 配置解析表单请求体:application/json。将json格式转成js对象

完美!!!(然而,并不是)

换种形式,就要换汤了。因为express.json()只能解析json形式的

app.use(express.urlencoded())   // 配置解析表单请求体:application/x-www-form-urlencoded

然后,因为需要保存到db.json中,所以也应该在db.js中封装一个saveDb()方法(app.js自然也要引入saveDb,这部分就不行出来了)

db.js

const fs = require('fs');
const { promisify } = require('util');  // 把callback形式的异步api转化成promise形式的
const path = require('path');const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);const dbPath = path.join(__dirname, './db.json');exports.getDb = async () => {const data = await readFile(dbPath, 'utf8');return JSON.parse(data)
}exports.saveDb = async db => {const data = JSON.stringify(db);await writeFile(dbPath, data)
}

添加任务的代码部分

app.post('/todos', async (req, res) => {try {// 1. 获取客户端请求体参数const todo = req.body;// 2. 数据验证if (!todo.title) {return res.status(422).json({error: 'The field title is required.'})}// 3. 数据验证通过后,把数据存储到db.json中const db = await getDb();const lastTodo = db.todos[db.todos.length - 1];   // 获取最后一个todotodo.id = lastTodo ? lastTodo.id + 1 : 1;   // 如果一个todo都没有,则添加的todo的id是1db.todos.push(todo)await saveDb(db);// 4. 发送响应res.status(200).json(todo);} catch (err) {res.status(500).json({error: err.message})}
})

小优化:上面可以发现,添加任务后,db.json格式很丑。其实就是把JavaScript 对象转换为 JSON 字符串时的问题,所以只需要在JSON.stringify()上下点功夫就行。

JSON.stringify()

exports.saveDb = async db => {const data = JSON.stringify(db, null, '  ');    // 这里第三个参数是两个空格,就是缩进按两个空格来await writeFile(dbPath, data)
}

眼尖的同学可能发现,添加的任务id和之前的位置不太一样。那么,有点小强迫症的我自然还是要在微操一手。

终于。。。

3.6 修改任务

app.patch('/todos/:id', async (req, res) => {try {// 1. 获取客户端请求体参数const todo = req.body;// 2. 查找到要修改的todoconst db = await getDb();const ret = db.todos.find(todo => todo.id === Number.parseInt(req.params.id))if (!ret) {   // 要修改的todo不存在return res.status(404).end();}Object.assign(ret, todo)    // 返回一个对象。如果添加的todo中有原本就有的属性,则修改属性值。如果没有,则新增属性await saveDb(db);res.status(200).json(ret);} catch (err) {res.status(500).json({error: err.message})}
})

修改原有属性:

新增属性

3.7 删除任务

app.delete('/todos/:id', async (req, res) => {try {const todoId = Number.parseInt(req.params.id);const db = await getDb();const index = db.todos.findIndex(todo => todo.id === todoId);   // 造出要删除的todo的索引if (index === -1) {   // 要删除的todo压根不存在return res.status(404).end();}db.todos.splice(index, 1);await saveDb(db);res.status(200).end()} catch (err) {res.status(500).json({error: err.message})}
})

4. res.end()和res.send()区别

官方说明:

  • res.end() 终结响应处理流程。(不过,也可以在结束的同时发送响应)

  • res.send() 发送各种类型的响应。

4.1 res.end()

结束响应流程。用于在没有任何数据的情况下快速结束响应。

  • 参数可以是buffer对象、字符串
  • 只接受服务器响应数据,如果是中文会乱码

4.2 res.send()

发送HTTP响应。

  • 参数可以是buffer对象、字符串、对象、数组
  • 发送给服务端时,会自动发送更多的响应报文头,包括Content-Type: text/html;charset=utf-8,所以中文不会乱码

res.send()发送对象响应

const express = require('express');const app = express();app.get('/', (req, res) => {res.send({name: 'clz'})
})app.listen(3000, () => {console.log('http://localhost:3000/');
})

改为用res.end()发送

res.send()发送中文(使用浏览器查看,postman可能自动设置了响应头)

res.send("测试")

改为res.edn()

学习参考视频:

Node.js 系列教程之 Express

Express(一) ——简单入门相关推荐

  1. windows下nodejs express安装及入门网站,视频资料,开源项目介绍

    windows下nodejs express安装及入门网站,视频资料,开源项目介绍,pm2,supervisor,npm,Pomelo,Grunt安装使用注意事项等总结 第一步:下载安装文件 下载地址 ...

  2. SpringCloud之Ribbon简单入门

    提出疑问 ① 如何在配置Eureka Client注册中心时不去硬编码Eureka Server的地址? ② 在微服务不同模块间进行通信时,如何不去硬编码服务提供者的地址? ③ 当部署多个相同微服务时 ...

  3. BizTalk 2006 简单入门示例程序(附源项目文件下载)

    BizTalk 2006 简单入门示例程序(附源项目文件下载) 为初学BizTalk Server 2006的开发人员,提供一个简单入门的示例程序,包括一个Receive Port.Send Port ...

  4. python如何读取mat文件可视化_python Matplotlib数据可视化(1):简单入门

    1 matplot入门指南 matplotlib是Python科学计算中使用最多的一个可视化库,功能丰富,提供了非常多的可视化方案,基本能够满足各种场景下的数据可视化需求.但功能丰富从另一方面来说也意 ...

  5. ARM NEON 编程简单入门1

    原文:http://blog.csdn.net/silentob/article/details/72954618  ARM NEON 编程简单入门1 NEON简介 NEON是适用于ARM Corte ...

  6. Python 简单入门指北(二)

    Python 简单入门指北(二) 2 函数 2.1 函数是一等公民 一等公民指的是 Python 的函数能够动态创建,能赋值给别的变量,能作为参传给函数,也能作为函数的返回值.总而言之,函数和普通变量 ...

  7. EChart.js 简单入门

    EChart.js 简单入门 最近有一个统计的项目要做,在前端的数据需要用图表的形式展示.网上搜索了一下,发现有几种统计图库. MSChart   这个是Visual Studio里的自带控件,使用比 ...

  8. 【我的区块链之路】- Hyperledger fabric的简单入门(四)链码的编写及调试

    [我的区块链之路]- Hyperledger fabric的简单入门(四)链码的编写及调试       之前的文章中我们有讲过了fabric的一些核心的配置文件的选项说明,讲过fabric 的网络启动 ...

  9. 基于vue-cli、elementUI的Vue超简单入门小例子

    基于vue-cli.elementUI的Vue超简单入门小例子 这个例子还是比较简单的,独立完成后,能大概知道vue是干嘛的,可以写个todoList的小例子. 开始写例子之前,先对环境的部署做点简单 ...

最新文章

  1. python是结构化语言_NLP是如何工作的:把自然语言(尽可能)结构化
  2. 动态注册客户端脚本的方法
  3. mysql为什么使用B+树
  4. java中集合的区别_Java中的集合与集合之间的区别
  5. 脉冲宽度测量程序 c51 c语言,基于C51单片机和LCD1602显示的超声波测距仪C语言程序...
  6. 网络设备监控-Catic添加H3C的监控图解
  7. JQuery七个常犯的错误
  8. 1 linux中解决文件已rm删除但空间不释放的案例
  9. 微信公号“架构师之路”学习笔记(一)-无限容量数据库架构设计(数据库分组、分片架构等)
  10. 【细胞分割】基于matlab中值滤波+分水岭法细胞计数【含Matlab源码 640期】
  11. Android原生人脸识别Camera2示例
  12. 技术人攻略访谈三十六-马鉴:玩Flash十五年,养出一只“神经猫”
  13. socks5代理ip购买_详解IP加速器的四种协议
  14. 计算机 分类号,中图法分类号(计算机,自动化)(CLC number (computer, automation)).doc...
  15. 2022 CCF中国软件大会(CCF Chinasoft)“约束求解与定理证明”论坛成功召开
  16. 厦门大学“网宿杯“17届程序设计竞赛决赛(同步赛) #题解 #题目都超有趣呀
  17. 2014人人校招 笔试总结
  18. java ssm框架调用微信_Java开发SSM框架微信退款的实现
  19. 英语和数学不好自学c语言,数学和英语不好,就不能学编程么?
  20. 三种方法教你让模糊照片秒变高清图

热门文章

  1. 关于支付宝CertificateException: X.509 not found的那些事~づ♡ど,JDK同样的版本之间也会有问题!
  2. 论文略读1《Direct training for spiking neural networks:faster,larger,better》
  3. Windows 2000/Xp 錯誤編號詳解
  4. 训练日记2019.11.11 莫队求区间众数
  5. 网站下载工具有哪些?
  6. 剑指offer:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
  7. matlab能不能求不定积分,用MATLAB求定积分
  8. 穷人 与 富人 思维 比较
  9. OPPO手机测试指令代码大全
  10. [PTA]习题9-3 平面向量加法