0


Express(一) ——简单入门

Express(一) ——简单入门

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

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

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

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

1. Hello World

首先,安装express到项目中

npm i express

然后,开始代码世界。

// 1. 加载expressconst 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.js

node app.js

nodemon

支持热更新。

image-20220207151828081

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

image-20220207161937956

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

image-20220207162928596

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

image-20220207163814050

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

image-20220207165808608

2.2.3 cookie

const express =require('express');const app =express();

app.get('/',(req, res)=>{
  res.cookie('name','clz');// 设置cookie

  res.end();// 结束响应})

app.listen(8080,()=>{
  console.log('http://localhost:8080/');})

image-20220207164941307

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

image-20220210123301562

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

image-20220207172638141

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

image-20220207184102851

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

image-20220208231000242

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 =awaitreadFile(dbPath,'utf8');returnJSON.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 =awaitgetDb();// 因为getDb是async的,所以所有形式都会被封装成Promise,所以获取数据都要await
    res.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 =awaitgetDb();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();})

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

image-20220208231856994

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

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

image-20220208232402601

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

image-20220208233059259

换种形式,就要换汤了。因为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 =awaitreadFile(dbPath,'utf8');returnJSON.parse(data)}

exports.saveDb=asyncdb=>{const data =JSON.stringify(db);awaitwriteFile(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 =awaitgetDb();const lastTodo = db.todos[db.todos.length -1];// 获取最后一个todo

    todo.id = lastTodo ? lastTodo.id +1:1;// 如果一个todo都没有,则添加的todo的id是1
    db.todos.push(todo)awaitsaveDb(db);// 4. 发送响应
    res.status(200).json(todo);}catch(err){
    res.status(500).json({
      error: err.message
    })}})

image-20220208235418267

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

JSON.stringify()

上下点功夫就行。

JSON.stringify()

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

image-20220209000445369

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

image-20220209001337659

终于。。。

image-20220209001401680

3.6 修改任务

app.patch('/todos/:id',async(req, res)=>{try{// 1. 获取客户端请求体参数const todo = req.body;// 2. 查找到要修改的todoconst db =awaitgetDb();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中有原本就有的属性,则修改属性值。如果没有,则新增属性awaitsaveDb(db);
    res.status(200).json(ret);}catch(err){
    res.status(500).json({
      error: err.message
    })}})

修改原有属性:

image-20220209003447970

新增属性

image-20220209003636418

3.7 删除任务

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

    db.todos.splice(index,1);awaitsaveDb(db);
    res.status(200).end()}catch(err){
    res.status(500).json({
      error: err.message
    })}})

image-20220209004455315

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

image-20220209213846401

改为用res.end()发送

image-20220209214613515

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

res.send("测试")

image-20220209215019228

**改为res.edn()**:

image-20220209215053732

学习参考视频:

Node.js 系列教程之 Express


本文转载自: https://blog.csdn.net/chilanzi/article/details/122973889
版权归原作者 赤蓝紫 所有, 如有侵权,请联系我们删除。

“Express(一) ——简单入门”的评论:

还没有评论