如何使用 Fastify 构建 REST API?
Fastify 是一个主要用于 JavaScript 后端开发的框架。它是您可以使用的最轻量级的后端框架之一,也是在想要避免使用 Express 和 Hapi 等较重的 Node 框架时首选它的主要原因之一。
自诞生以来,Fastify 已发布了多个版本。在最新版本中,我们甚至能够验证传入和传出的请求以及请求参数。毫不奇怪,开发 Fastify 的人员声称它是您可以使用的最快的 Node.js 框架,与 Koa、Hapi 和 Express 等其他框架相比。
Fastify 框架之所以广受欢迎,主要是因为其轻量级的设计。Fastify 与原生框架的不同之处在于,它将所有内容都视为插件,而在 JavaScript 中,我们将所有内容都视为对象。这反过来又使我们能够快速将项目的特定功能封装为插件,并将其分发到其他项目中。
在本教程中,我们将通过示例学习 Fastify 框架的以下方面
如何创建一个简单的 Fastify 服务器
如何在 Fastify 中定义 API 路由
如何为我们的请求添加模式验证
如何定义钩子
需求和安装
使用 Fastify 构建 REST API 的第一步是在项目中创建项目并安装 Fastify 作为依赖项。
您首先需要以下内容:
最新版本的 Node.js
一个用于测试端点的工具,例如 PostMan 或 cURL。
要验证 Node 版本,您可以运行下面显示的命令。
node -v
运行上述命令后,您将在终端上获得以下**输出**。
v16.16.0
如果您以某种方式没有获得相同的输出,则需要首先在本地机器上安装“node”,然后继续执行下面显示的命令。
下一步是创建一个空的 Node 项目。如果您还没有项目,则可以使用下面显示的命令来初始化它:
npm init -y
在终端中运行上述命令后,将创建一个“package.json”文件,该文件将跟踪所有依赖项以及您可能需要的脚本。
为了能够使用 Fastify 框架,我们需要将其导入为依赖项,我们可以使用下面显示的命令来实现:
npm i fastify --save
这样做后,我们应该在“package.json”文件中的依赖项中看到 Fastify。
现在我们已经准备好进入构建 REST API 的不同阶段。
如何创建简单的 Fastify 服务器?
要创建简单的 Fastify 服务器,我们首先需要创建一个 JS 文件。假设我们要将文件命名为“index.js”。要创建同一个文件,请在终端中运行以下命令:
touch index.js
index.js
现在在您喜欢的代码编辑器中打开您的项目,并在“index.js”文件中编写以下代码。
// to require the framework const app = require('fastify')({ logger: true }) // to declare a single route app.get('/', function (req, reply) { reply.send({ Welcome: 'TutorialsPoint' }) }) // Run the server! app.listen({ port: 3000 }, (err, address) => { if (err) { app.log.error(err) process.exit(1) } app.log.info(`The server is listening on ${address}`) })
在上面的示例中,我加载了 Fastify 应用程序对象,然后在该对象中,我启用了日志记录。稍后,我声明了一个仅回复单个响应的单个路由,即“Welcome: TutorialsPoint”。最后一个代码块描述了我们在端口 3000 上侦听的情况。
要运行上述代码,我们需要在终端中运行以下命令。
node index.js
运行上述命令后,我们的服务器将在以下端点上启动并运行。
https://127.0.0.1:3000
现在要测试它,我们可以使用 PostMan 或 cURL,或者简单地访问浏览器,因为它是一个简单的 GET 请求。
我将使用 cURL 命令。请考虑下面显示的命令:
curl https://127.0.0.1:3000
运行上述命令后,您将在终端上获得以下**输出**。
{"Welcome":"TutorialsPoint"}
创建简单的 Fastify 服务器的第一步已完成,现在让我们学习如何在 API 中定义路由。
如何在 Fastify 中定义 API 路由?
如果 API 中没有多个路由,那么它就没有意义。在我们的 API 中,我们将有多个路由,因为我们的 REST API 是关于获取不同书籍及其作者和标题的详细信息。
在我们的 REST API 示例中,我们将定义以下路由。
**GET** – 所有书籍位于 /api/books
**GET** – 单本书位于 /api/book/:id
**POST** – 在 /api/books 添加一本书
**PUT** – 在 /api/books/:id 更新一本书
**DELETE** – 在 /api/delete/:id 删除一本书
在定义路由之前,有必要为这些路由定义控制器。
创建书籍控制器
为了使我们的代码模块化和简洁,让我们创建一个名为 controller 的目录,并在该目录中创建一个名为 books.js 的文件。
books.js
在 books.js 文件中,粘贴下面显示的代码:
let books = [{ id: 1, title: 'Maharana Pratap : The Invincible Warrior', author: 'Rima Hooja' }, { id: 2, title: 'Prithviraj Chauhan - A Light on the Mist in History', author: 'Virendra Singh Rathore' }, { id: 3, title: 'Rani Laxmibai: Warrior-Queen of Jhansi', author: 'Pratibha Ranade' } ] // Handlers const getAllBooks = async (req, reply) => { } const getBook = async (req, reply) => { const id = Number(req.params.id) const book = books.find(book => book.id === id) return book } const addBook = async (req, reply) => { const id = books.length + 1 const newBook = { id, title: req.body.title, author: req.body.author } books.push(newBook) return newBook } const updateBook = async (req, reply) => { const id = Number(req.params.id) books = books.map(book => { if (book.id === id) { return { id, title: req.body.title, author: req.body.author } } }) return { id, title: req.body.title } } const deleteBook = async (req, reply) => { const id = Number(req.params.id) books = books.filter(book => book.id !== id) return { msg: `Blog with ID ${id} is deleted` } } module.exports = { getAllBooks, getBook, addBook, updateBook, deleteBook }
在上面的代码中,定义了不同的处理程序。这些是:
**getAllBooks** – 获取包含所有书籍的响应
**getBook** – 从书籍 ID 获取特定书籍。
**addBook** – 将书籍添加到书籍对象的书籍数组中。
**updateBook** – 更新书籍
**deleteBook** – 从书籍对象数组中删除书籍。
需要注意的是,为了节省时间并使控制器保持简单,我使用对象数组来存储书籍信息,而不是使用数据库。
下一步是创建路由,以便我们可以在其中使用这些控制器函数。要创建路由,我们将遵循类似的文件夹结构。
让我们在项目的根目录中创建一个名为“routes”的目录。在“routes”目录中,让我们创建一个名为“books.js”的文件。
books.js
现在,将以下代码粘贴到“books.js”文件中。
const booksController = require('../controller/books'); const routes = [{ method: 'GET', url: '/api/books', handler: booksController.getAllBooks }, { method: 'GET', url: '/api/books/:id', handler: booksController.getBook }, { method: 'POST', url: '/api/books', handler: booksController.addBook }, { method: 'PUT', url: '/api/books/:id', handler: booksController.updateBook }, { method: 'DELETE', url: '/api/books/:id', handler: booksController.deleteBook } ] module.exports = routes
在上面的代码中,我们定义了上面提到的所有路由,并且在每个路由中,我都有一个关联的处理程序来处理该路由。
现在,在我们能够运行路由并对其进行测试之前,唯一剩下的步骤是首先将这些路由添加到 app 对象中,我们可以在项目根目录中存在的 index.js 文件中执行此操作。
index.js
// to require the framework const app = require('fastify')({ logger: true }) // to declare a single route app.get('/', function (req, reply) { reply.send({ Welcome: 'TutorialsPoint' }) }) // Register routes to handle blog posts const bookRoutes = require('./routes/books') bookRoutes.forEach((route, index) => { app.route(route) }) // Run the server! app.listen(3000, (err, address) => { if (err) { app.log.error(err) process.exit(1) } app.log.info(`The server is listening on ${address}`) })
现在,路由定义已完成。要测试这些路由,我们首先需要使用下面显示的命令运行应用程序:
index.js
运行上述命令后,我们就可以打开浏览器并点击 url **https://127.0.0.1:3000/api/books/1**,这将调用控制器/books.js 文件中的 **getBook** 处理程序函数,并将返回“id = 1”的书籍。
如何为我们的请求添加模式验证?
在上一节中的代码中,我们没有请求验证,这意味着我们可以将任何内容传递到请求中,它将被视为有效并进入我们的代码,但这通常不是我们想要的。
现在,假设我们想要确保传递到 getBook 端点的 id 仅为对象类型,而不是任何其他类型,为此,我们可以在控制器中编写请求验证。
请考虑下面显示的代码片段:
const getBookValidation = { params: { id: { type: 'object' } }, response: { 200: { type: 'object', properties: { id: { type: 'integer' }, title: { type: 'string' }, author: { type: 'string'} } } } }
在上面的验证中,我们确保在 params 中传递的 id 字段为对象类型,而不是任何其他类型。
此外,我们还需要在控制器/books.js 中的 modules.export 中添加函数 getBookValidation。
完成控制器部分后,下一步是将函数添加到路由中,以便当我们收到该路由的请求时,我们的验证才能生效。为此,我们需要在 getBook 路由内部编写下面显示的以下行。
schema: booksController.getBookValidation,
books.js
const booksController = require('../controller/books'); const routes = [{ method: 'GET', url: '/api/books', handler: booksController.getAllBooks }, { method: 'GET', url: '/api/books/:id', schema: booksController.getBookValidation, handler: booksController.getBook }, { method: 'POST', url: '/api/books', handler: booksController.addBook }, { method: 'PUT', url: '/api/books/:id', handler: booksController.updateBook }, { method: 'DELETE', url: '/api/books/:id', handler: booksController.deleteBook } ] module.exports = routes
现在,让我们测试路由。我们需要运行 index.js 文件,然后访问 URL https://127.0.0.1:3000/api/books/{1},这样就能按预期工作。但是,如果你尝试在 id 参数中传递一个对象,比如访问 URL https://127.0.0.1:3000/api/books/1, 那么你将在浏览器中看到以下验证错误。
{ "statusCode": 400, "error": "Bad Request", "message": "params/id must be object" }
如何定义钩子?
在 Fastify 中,我们可以通过使用 addHook 方法来定义钩子。
考虑以下示例,我们将定义一个钩子,打印我们 REST API 中注册的所有路由。
index.js 添加钩子
app.addHook('onRoute', (routeOptions) => { console.log(`Routes that are registered are: ${routeOptions.url}`) })
以上代码片段需要添加到 "index.js" 文件中,然后当我们运行该文件时,我们将获得以下输出
输出
Routes that are registered are: / Routes that are registered are: / Routes that are registered are: /api/books Routes that are registered are: /api/books Routes that are registered are: /api/books/:id Routes that are registered are: /api/books/:id Routes that are registered are: /api/books Routes that are registered are: /api/books/:id Routes that are registered are: /api/books/:id
结论
在本教程中,我们学习了如何使用 Fastify 创建 REST API。