使用 Node.js 和 Express 构建 Web API

Web API

使用 Web 作为运行应用程序的平台:任何人都可以使用遵守 HTTP浏览器、客户端、软件访问你的应用程序

  • Node.js 有一个名为 HTTP 的核心模块,可帮助构造 Web 应用程序
  • HTTP 模块的速度不及使用框架那么快,Node.js 有许多 Web 框架,例如 Hapi、Fastify、Koa、Express

对 Web 中数据的基本概念

  • 数据存储:一般在文件系统数据库
  • 数据访问:使用 HTTP 的 Web 应用和 API 来提供该数据

在生成 Web 应用程序和 API 时需要考虑:

  • 路由:应用程序根据 URL 地址的不同部分划分为不同的部分,例如 https://baike.baidu.com/item/ 中路径部分 /item
  • 支持不同内容类型:要提供的数据可能以不同的格式存在
  • 身份验证/授权:某些数据可能是敏感数据
  • 读取/写入数据:用户通常需要同时查看和向系统添加数据
  • 成本:若要高效地创建 Web 应用程序和 API,请选择为常见问题提供解决方案的工具和库

Node.js 中的 http 模块

  • 帮助管理请求的对象

    • http.Server: 表示 HTTP Server 的实例,需要指示此对象侦听特定端口地址上的不同事件
    • http.IncomingMessage: 此对象是由 http.Server 或 http.ClientRequest 创建的可读流,使用它访问状态、标头和数据
    • http.ServerResponse: 此对象是 HTTP 服务器在内部创建的流,此类定义响应应有的外观
  • 示例

    const http = require('http');
    const PORT = 3000; // createServer() 方法创建 http.Server 类的实例,其中箭头函数是 createServer() 需要实现的回调函数
    // 1. 箭头函数参数列表:req,res 分别表示请求和响应
    // 2. 箭头函数的函数体:需要对请求和响应进行处理,处理根据项目需求而定
    // 3. 不了解请求或响应,建议温习或学习计算机网络相关知识,也可单独学习 HTTP 协议的相关内容
    const server = http.createServer((req, res) => {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('hello world');
    }); // 开始侦听请求,此时服务器即可接受客户端请求
    server.listen(PORT, () => {
    console.log(`listening on port ${PORT}`)
    })
  • Node.js 流

    流 (stream) 不是 Node.js 概念,而是操作系统概念,它定义数据来回传输的方式

    流是 Node.js 中的基本数据结构

    • 可以读取和写入数据
    • 可以发送和接收消息或事件

    之前的 reqres 参数都是流

    • 使用 on() 方法侦听来自客户端请求的传入数据
    • 使用 end() 方法发送回客户端的数据

创建 Express 框架 Web 应用程序

Express 框架

为什么要将 Express 作为构建下一个应用的框架?

  • 过多便捷高效的功能
  • 使复杂性抽象化,使整个开发体验变得更加容易
  • 解决常见的 Web 问题,有助于解决路由管理、缓存、重定向等
  • 由数百万开发者信任,意味着维护性和保障

Express 中的路由管理

  • Express 框架使用 URL、路由、HTTP 谓词进行路由管理

  • Express 可帮助注册路由,并将它们与适当的 HTTP 谓词配对以组织 Web 应用程序

  • 示例

    // 该请求具有与 HTTP 谓词 get 关联的地址 /products
    app.get('/products', (req, res) => {
    // handle the request
    }) // 看待 get 对 /products 和 post 对 /products 不同
    app.post('/products', (req, res) => {
    // handle the request
    })

Express 支持许多不同的内容格式,这些可以返回给调用客户端

  • 若要返回纯文本,可使用 send() 方法

  • 对于 JSON 等其他类型的数据,可通过 json() 方法确保内容类型和数据转换正确

  • 示例

    // Express 框架
    res.send('plain text')
    res.json({ id: 1, name: "Catcher in the Rye" }) // 原生
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ id: 1, name: "Catcher in the Rye" }));

若要开始使用 Express 框架开发 Node.js 应用程序,建议首先初始化 Node.js 项目以便下载的任何依赖项最后都在 package.json 文件中(这是针对为 Node.js 运行时开发的应用的一般建议),Express 框架创建 Web 应用程序的基本步骤:

  • 实例化应用
  • 定义路由和路由处理程序
  • 配置中间件
  • 启动应用

Express 示例

一个基本的 Express Web 程序

  • 初始化项目:在命令行随便创建一个目录 express-web,进入目录执行 npm init -y && npm install express,其中 && 表示前一个命令顺利执行后一个才能执行

  • 在 package.json 文件中确认 express 安装

    "dependencies": {
    "express": "^4.18.2"
    }
  • 创建服务端,先创建名为 app.js 的文件

    /**
    * app.js
    */
    const express = require('express'); const port = 3000;
    // 通过调用 express() 方法创建 Express 应用程序的实例
    const app = express(); // 设置 Web 应用的根路由 '/',访问根路由会响应文本 'Hello World!'
    app.get('/', (req, res) => res.send('Hello World!')); // 通过调用 listen() 方法启动 Web 应用
    app.listen(port, () => console.log(`Example app listening on port ${port}!`));
  • 在终端中,运行 node app.js 命令以启动 Express Web 应用

    • 应会看到以下输出 Example app listening on port 3000!,此时程序是处于监听状态
    • 在浏览器中访问 http://localhost:3000,会看到以下输出 Hello World!
  • 在终端中,按 Ctrl+C 即可停止 Express Web 程序

Web 应用返回 JSON 数据

  • 打开 app.js 文件,在原有的基础上添加以下代码

    app.get("/products", (req,res) => {
    const products = [
    {
    id: 1,
    name: "hammer",
    },
    {
    id: 2,
    name: "screwdriver",
    },
    {
    id: 3,
    name: "wrench",
    },
    ]; res.json(products);
    });
  • 再次运行 app.js,浏览器访问 http://localhost:3000/products,可以看到

    [{"id":1,"name":"hammer"},{"id":2,"name":"screwdriver"},{"id":3,"name":"wrench"}]

使用中间件管理请求生命周期

当请求到达 Web 应用程序时,你可能需要验证用户是否已登录或是否允许他们查看特定资源

考虑将请求作为一系列步骤来处理

  • pre 请求:调查用户是否通过请求标头发送了正确的凭据
  • 构造响应:与某种数据源(如数据库或终结点)通信
  • post 请求: 一个可选步骤,用于在请求处理后运行一段代码

Express 框架对以此方式处理请求提供内置支持

  • Express 中的 pre 或 post 请求称为“中间件”

  • 若要运行 pre 或 post 请求,需要对 Express 实例化对象实现 use() 方法

  • 示例

    app.use((req, res, next) => {});
    • req: 包含请求标头和调用 URL 的传入请求
    • res: 用于写入要发送回调用客户端的标头和数据等信息的响应流
    • next: 指示请求正常并已准备好处理的参数,如果未调用 next() 则请求的处理将停止

如果你的路由从中间件运行 pre 或 post 请求中生效,请设置它:

  • 需要在请求之前运行的中间件(pre 请求)定义在实际请求前
  • 需要在请求之后运行的中间件(post 请求)定义在实际请求后
// 有效设置
app.use((req, res, next) => {
// Pre request,进行验证
})
app.get('/protected-resource', () => {
// Handle the actual request,Pre 中验证通过方可执行
})
app.use((req, res, next) => {
// Post request,可选
}) // 没有使用
app.get('/login', () => {}) // 将 pre 请求中间件代码作为处理请求的参数来运行
app.get(
'/route',
() => {
// Pre request middleware
},
() => {
// Handle the actual request
}
);

实际中使用的身份验证/授权功能需要比我们的示例更可靠一些,有必要了解 OAuth、JSON Web 令牌、JWT 等概念和库 bcrypt,以确保为应用提供适当的保护级别

在 Node.js 中使用 JavaScript 路由管理

URL 是用户在浏览器等客户端中输入的用于查找特定服务器和特定资源的地址

scheme:[//authority]path[?query][#fragment]
  • Scheme: 协议
  • authority: 颁发机构,域名、主机、IP
  • path: 路径部分包含零至多个段,是服务器中以站点为根路径
  • query: 可以通过请求特定页面中的多条记录来进一步筛选数据
  • fragment: 请求内容更加具体

路由是 URL 的一个分段,通常指向特定资源

  • Express 定义路由,并将不同处理程序与它们关联

    // 路由参数写入请求对象 req 上的 params 属性中
    app.get('/products/:id', (req, res) => {
    // 如果请求 /products/144,那么 req.params.id=144
    })
  • Express 具有一种处理查询参数的简单方法

// 查询参数会写入到 res 请求对象上的 query 查询对象
app.get('/products', (req, res) => {
// 如果请求 /products?page=1&pageSize=20,那么 res.query 等价 {page: 1, pageSize: 20}
})

实现对读取和写入数据的支持

在 products 资源上实现简单的 CRUD API

const express = require('express')
const app = express()
const port = 3000 // 导入正文分析器,需要将传入数据转换为可读的格式
let bodyParser = require('body-parser');
// 将传入的正文数据分析为预期格式
app.use(bodyParser.json()); // 存储数据,但一般此处是由数据库
let products = []; // C 创建
app.post('/products', function (req, res) {
const newProduct = { ...req.body, id: products.length + 1 }
products = [ ...products, newProduct]
res.json(newProduct);
}); // R 读取
app.get('/products', (req, res) => {
res.json(products);
}) // U 更新
app.put('/products', function (req, res) {
let updatedProduct;
products = products.map(p => {
if (p.id === req.body.id) {
updatedProduct = { ...p, ...req.body };
return updatedProduct;
}
return p;
})
res.json(updatedProduct);
}); // D 删除
app.delete('/products/:id', function (req, res) {
const deletedProduct = products.find(p => p.id === +req.params.id);
products = products.filter(p => p.id !== +req.params.id);
res.json(deletedProduct);
}); // 启动,进行监听
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

Express 有一种 route() 方法,可以对代码进行分组使其更易于阅读

const express = require('express')

const app = express()
const port = 3000 let bodyParser = require('body-parser');
app.use(bodyParser.json()); let products = []; // 下面不同处
app.route('/products')
.get((req, res) => {
res.json(products);
})
.post((req, res) => {
const newProduct = { ...req.body, id: products.length + 1 }
products = [...products, newProduct]
res.json(newProduct);
})
.put((req, res) => {
let updatedProduct;
products = products.map(p => {
if (p.id === req.body.id) {
updatedProduct = { ...p, ...req.body };
return updatedProduct;
}
return p;
})
res.json(updatedProduct);
})
.delete((req, res) => {
const deletedProduct = products.find(p => p.id === +req.body.id);
products = products.filter(p => p.id !== +req.body.id);
res.json(deletedProduct);
}); app.listen(port, () => console.log(`Example app listening on port ${port}!`))

使用 Node.js 和 Express 构建基本的 Web API的更多相关文章

  1. 一个迷你的 Node.js 基于 Express 的 MVR 模式的 API工程 的分析

    1. 工程说明 该工程是基于 Express 库,编写的一个 API 查询返回的一个微型应用. API Resource 就是把 API 的内容当做网络资源去处理.工程中的路由访问也是返回 API 内 ...

  2. 如何设计一个基于Node.js和Express的网站架构?

    前言 今年七月份,我和几个小伙伴们合伙建立了一个开发团队.业务开展如火如荼的同时,团队宣传就提上了日程,所以迫切需要搭建公司网站出来.确定目标后我们就开始考虑如果构建一个企业网站.先是进行业内调查,看 ...

  3. Node.js基于Express框架搭建一个简单的注册登录Web功能

    这个小应用使用到了node.js  bootstrap  express  以及数据库的操作 :使用mongoose对象模型来操作 mongodb 如果没了解过的可以先去基本了解一下相关概念~ 首先注 ...

  4. Node.js系列-express(上)

    前言 Node.js系列的第一篇:http,大概描述了通过使用node.js内置的api创建一个服务并监听request实现简单的增删改查.现在,我们就通过通读express官网及使用express框 ...

  5. Code Your First API With Node.js and Express: Set Up the Server

    How to Set Up an Express API Server in Node.js In the previous tutorial, we learned what the REST ar ...

  6. Node.js、express、mongodb 实现分页查询、条件搜索

    前言 在上一篇Node.js.express.mongodb 入门(基于easyui datagrid增删改查) 的基础上实现了分页查询.带条件搜索. 实现效果 1.列表第一页. 2.列表第二页 3. ...

  7. Node.js、express、mongodb 入门(基于easyui datagrid增删改查)

    前言 从在本机(win8.1)环境安装相关环境到做完这个demo大概不到两周时间,刚开始只是在本机安装环境并没有敲个Demo,从周末开始断断续续的想写一个,按照惯性思维就写一个增删改查吧,一方面是体验 ...

  8. node.js和express.js安装和使用步骤 [windows]

    PS: NODEJS:https://nodejs.org NPM:https://www.npmjs.com/ 一.node.js安装与配置 到https://nodejs.org/en/downl ...

  9. Feathers JS – 基于 Express 构建数据驱动的服务

    Feathers 是一个轻量的 Web 应用程序框架,基于 NodeJS 最流行​​的 Web 框架——Express.这使得它很容易使用 socket.io 来创建 RESTful Web 服务和实 ...

  10. node.js框架express的安装

    node.js框架express的安装 首先假定你已经安装了 Node.js,接下来为你的应用创建一个目录,然后进入此目录并将其作为当前工作目录. $ mkdir myapp $ cd myapp 通 ...

随机推荐

  1. Mysql 存储引擎的区别以及索引查询失效的情况

    存储引擎:就是指表在计算机上的存储方式.可以通过 SHOW ENGINES; 命令查询支持的存储引擎. alter table test engine= innodb/memory/myisam/ar ...

  2. MyBatis抛出BindingException异常可能是你忘了配置资源拷贝

    最近博主在搭建Mybatis项目时遇到了一问题,在一切配置妥当后,开始运行测试代码,但是此时控制台无情的抛出了异常: 开始寻找问题根源 咦?难道是我哪里写错,我的第一反应是我的xml配置文件哪里写错了 ...

  3. python利用flux基本读写influxDB

    1.读取 QuerApi 形式 python 利用 flux 语句查询 influxdb 数据. https://influxdb-client.readthedocs.io/en/latest/ap ...

  4. 在kubernetes里使用AppArmor限制容器对资源的访问

    目录 一.系统环境 二.前言 三.AppArmor简介 四.AppArmor和SELinux的区别 五.使用AppArmor限制nginx程序访问目录 5.1 安装nginx 5.2 修改nginx的 ...

  5. iOS使用SignalR客户端代码典范-桥接web SignalR 客户端库

    一.SignalR介绍 SignalR是微软基于.Net提供的一个开源实时Web RPC库,可以用在web实时通信的需求上面,比如聊天,web数据更新 SignalR的接口使用十分简单 由于最近的一个 ...

  6. svn递归添加目录下面所有文件

    进入根目录:$ svn add * --force A foo.c A somedir/bar.c A otherdir/docs/baz.doc

  7. 「C++」深度分析C++中i++与++i的区别

    大家好,我是Charzie.在C++编程中,i++和++i是两个常见的自增运算符,用于将变量的值增加1(有时与i+=1效果一样).然而,虽然它们的功能看似相似,但在实际使用中却存在显著的区别.本博客将 ...

  8. wordpress 折腾记

    今天我看到一篇个人博客,我想建个人网站的心又动了. 虽说博客园已经很符合我的预期了,但我还是一直很想做一个个人网站做一些个性化的东西,今天试试用用wordpress搭建一个wordpress网站 介绍 ...

  9. mvn 打包报错:no compiler is provided in this environment

    最近公司换了电脑,系统也从 win7 升级到 win11,开发环境都重新安装了一遍,然后在 idea 用mvn 执行打包命令 mvn clean package 报错: no compiler is ...

  10. ABC340

    E 我们可以知道每一个点在每一轮加多少,具体如下: 假如现在操作的点的为 \(k\).那么所有的数都至少会加 \(\dfrac{A_k}{n}\).但是肯定有剩的,剩了 \(A_k \mod n\). ...