如何使用Node.js,MongoDB和Fastify构建快速的GraphQL API

This tutorial is part two of a four part series, which aims to take you from scratch to deploying a fully functional full stack application.

本教程是一个由四个部分组成的系列文章的第二部分,该系列文章旨在使您从头开始学习部署完整功能的全栈应用程序。

  • Part 1: How to build blazing fast REST APIs with Node.js, MongoDB, Fastify and Swagger

    第1部分:如何使用Node.js,MongoDB,Fastify和Swagger构建出色的快速REST API
  • Part 2: How to build a blazing fast GraphQL API with Node.js, MongoDB, Fastify and GraphQL! (You are here.)

    第2部分:如何使用Node.js,MongoDB,Fastify和GraphQL构建快速的GraphQL API! (你在这里。)

  • Part 3: Coupling Vue.js with a GraphQL API.

    第3部分:将Vue.jsGraphQL API耦合。

  • Part 4: Deploying a GraphQL API and Vue.js frontend application.

    第4部分:部署GraphQL APIVue.js前端应用程序

The first part of the series is available here and the source code for the application can be found here.

该系列的第一部分可在此处找到,该应用程序的源代码可在此处找到。

In this part we will revisit the models, controllers and routes from part one and then integrate GraphQL into the application. As a bonus we will also use Faker.js to create some fake data and seed the database.

在这一部分中,我们将从第一部分回顾模型控制器路由 ,然后将GraphQL集成到应用程序中。 作为奖励,我们还将使用Faker.js 创建一些虚假数据并播种数据库

介绍: (Introduction:)

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.

GraphQL是API的查询语言,是用于使用现有数据来完成这些查询的运行时。

Every GraphQL query goes through three phases: the queries are parsed, validated and executed.

每个GraphQL查询都经历三个阶段:解析,验证和执行查询。

GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need, makes it easier to evolve APIs over time, and enables powerful developer tools. Learn More.

GraphQL为您的API中的数据提供了完整且易于理解的描述,使客户可以准确地询问他们的需求,使随着时间的推移更容易开发API,并启用强大的开发人员工具。 了解更多

先决条件... (Prerequisites…)

If you have completed the first part of this series, you should be up to speed with beginner/intermediate JavaScript knowledge, Node.js, Fastify.JS and MongoDB (Mongoose).

如果您已完成本系列的第一部分,那么您应该掌握初学者/中级JavaScript知识, Node.js,Fastify.JSMongoDB(Mongoose)的知识。

To follow along, you will need to complete part one of this series or grab the code from Git, although I would highly recommend at least skimming through part one.

为了继续学习,您将需要完成本系列的第1部分或从Git中获取代码,尽管我强烈建议至少略读第1部分

让我们开始吧! (Let’s get started!)

Clone the repo for part one (skip this step if you followed part one and you are continuing with your own code) by opening your terminal, navigating to your project directory and executing each of the following lines of code:

通过打开终端,导航到您的项目目录,并克隆第一部分的仓库(如果遵循了第一部分,则跳过此步骤,如果您继续自己的代码)。 执行以下每行代码:

git clone https://github.com/siegfriedgrimbeek/fastify-api.git
cd fastify-api

So now that we have a copy of the codebase we will update our packages and package.json file by running the following code:

现在,我们有了代码库的副本,我们将通过运行以下代码来更新packagepackage.json文件:

sudo npm i -g npm-check-updates
ncu -u
npm install

First we globally install the npm package “npm-check-updates” and then we use this package to automatically update our package.json file with the latest package versions and then we install/update all our npm modules by running npm install.

首先,我们在全球范围内安装npm软件包“ npm-check-updates ”,然后使用该软件包自动更新具有最新软件包版本的package.json文件,然后通过运行npm install安装/更新所有npm模块

This is done to ensure that everyone completing the tutorial is working with the same package versions.

这样做是为了确保完成本教程的每个人都使用相同的软件包版本。

重构我们的服务器并启动应用程序! (Refactor our server and start the app!)

As with all software solutions, as the solution grows, developers often need to revisit and refactor the code.

与所有软件解决方案一样,随着解决方案的发展,开发人员经常需要重新访问重构代码。

In the src directory we will create a new file called server.js:

src目录中,我们将创建一个名为server.js的新文件:

cd src
touch server.js

Add the following code code to the server.js file:

将以下代码添加到server.js文件:

We have now extracted the logic that starts the server to the server.js file, allowing us to reuse this code throughout the project.

现在,我们已经将启动服务器的逻辑提取到server.js文件中,从而使我们可以在整个项目中重用此代码。

Next we need to update our index.js file in the src directory:

接下来,我们需要更新src目录中的index.js文件:

We will revisit the index.js file, once we setup and configure GraphQL.

设置和配置GraphQL之后 ,我们将重新访问index.js文件

Start the Fastify server by running the following code in your terminal:

通过在终端中运行以下代码来启动Fastify服务器:

npm start

Note that there is no default route setup so for now, navigating to http://localhost:3000/ will result in the server returning a 404 error which is correct.

请注意,没有默认路由设置,因此,目前,导航到http:// localhost:3000 /将导致服务器返回正确的404错误。

启动MongoDB并更新模型 (Start MongoDB and update the models)

Let’s extend the existing model to also include Services and Owners. The below diagram below demonstrates the relationships between the collections:

让我们扩展现有模型,使其也包括服务所有者。 下图显示了集合之间的关系:

  • One car can have one owner.

    一辆车可以有一个所有者。
  • One owner can have many cars.

    一个所有者可以拥有许多汽车。
  • One car can have many services.

    一辆车可以提供多种服务。

Revisit the Car.js file in the models directory and update it as follows:

重新Car.js models目录中的Car.js文件,并进行如下更新:

Create two new files in the models directory, Owner.js and Service.js and add the following code to the files respectively:

models目录中创建两个新文件Owner.jsService.js ,并将以下代码分别添加到文件中:

Owner.js

Owner.js

Service.js

Service.js

// External Dependancies
const mongoose = require("mongoose")
const ObjectId = mongoose.Schema.Types.ObjectId

const serviceSchema = new mongoose.Schema({
  car_id: ObjectId,
  name: String,
  date: String
})

module.exports = mongoose.model("Service", serviceSchema)
view rawService.js hosted with ❤ by GitHub

There are no new concepts used in the above code. We have just created standard Mongoose Schemas, as with the Car.js model.

上面的代码中没有使用新概念。 与Car.js模型一样,我们刚刚创建了标准的Mongoose Schema

重新访问汽车控制器并创建其他控制器 (Revisit the Car Controller and create the additional controllers)

There are some slight changes to the carController.js so navigate to the controllers directory and update your file as per below:

carController.js进行了一些细微更改,因此请导航至controllers目录并按照以下说明更新文件:

Create two new files in the controllers directory, serviceController.js and ownerController.js, and add the following code to the files respectively:

controllers目录中创建两个新文件, serviceController.jsownerController.js ,并将以下代码分别添加到文件中:

serviceController.js

serviceController.js

ownerController.js

ownerController.js

The biggest change to the controllers is how we get the parameters:

控制器的最大变化是如何获取参数:

const id = req.params === undefined ? req.id : req.params.id
const updateData = req.params === undefined ? req : req.params

The above code is called a “conditional (ternary) operatorand is used as shorthand for the following if statement:

上面的代码称为“ 条件(三元)运算符 ,并用作以下if语句的简写:

let id

if (req.params === undefined) {

id = req.id

} else {

id = req.params.id

}

We are using the ternary operator to accommodate requests from both the REST API and the GraphQL API, as they have a slightly different implementation.

我们正在使用三元运算符来适应来自REST APIGraphQL API的请求,因为它们的实现略有不同。

是时候用一些假数据来播种数据库了! (Time to seed the database with some fake data!)

In the src directory let’s create a new directory and file by running the following code:

src目录中,让我们通过运行以下代码来创建新目录和文件:

mkdir helpers
touch seed.js

Add the following code to the seed.js file:

将以下代码添加到seed.js文件:

Let’s break down this mountain of code:

让我们分解一下这堆代码:

First we import two external libraries, Faker.js which is used to generate fake data and Boom, which is used to throw http friendly error objects.

首先,我们导入两个外部库Faker.js 用于生成伪造的数据和用于抛出http友好错误对象的Boom

Then we import the server.js file which will spin up an instance of our server allowing us to interact with the models.

然后,我们导入server.js文件,该文件将启动服务器实例,从而使我们可以与模型进行交互。

We then declare two arrays with fake data, cars and serviceGarages.

然后,我们用假数据声明两个数组carsserviceGarages

Then we import the models and declare three functions (generateOwnerData, generateCarData, generateServiceData) which each return an array of objects with the owner, car and service data respectively.

然后,我们导入models并声明三个函数( generateOwnerDatagenerateCarDatagenerateServiceData ),每个函数分别返回一个拥有所有者汽车服务数据的对象数组。

Once the Fastify.js instance is ready we use the Mongoose insertMany() function to insert the generated arrays into the database. The function then returns an array of objects containing the original object data and ids of the each record.

一旦Fastify.js实例准备就绪,我们将使用Mongoose insertMany()函数将生成的数组插入数据库。 然后,该函数返回一个对象数组,其中包含原始对象数据和每个记录的ids

We use the JavaScript Map function to create an array of ids owners and cars arrays. We use the ownersIDs array for when generating car data and we use the carsIds array when generating service data, they are passed into the respective functions and then values are randomly selected from them.

我们使用JavaScript Map函数创建ids 所有者数组和cars数组。 我们使用ownersIDs阵列生成车数据时,我们使用carsIds产生业务数据时阵列,它们被传递到相应的函数,然后值进行随机选自它们中的。

Lastly we need to install the Faker.js package and add the seed task to our package.json file.

最后,我们需要安装Faker.js包,并将种子任务添加到我们的package.json文件中。

We can add the Faker.js package by navigating to the root directory and running the following code:

我们可以通过导航到根目录并运行以下代码来添加Faker.js包:

npm i faker -D

We then add the following to the package.json file:

然后,将以下内容添加到package.json文件:

...

"scripts": {

...

"seed": "node ./src/helpers/seed.js"

},

...

That’s it! We can now run our seeding script from the project root directory with the following code:

而已! 现在,我们可以使用以下代码从项目根目录运行种子脚本:

npm run seed

If you are using MongoDB Compass (you should), you will see the data in your database:

如果您正在使用MongoDB Compass (应使用),则将在数据库中看到数据:

GraphQL安装,设置和测试 (GraphQL installation, setup and testing)

Let’s get started by navigating to the root directory and running the following code:

让我们从导航到根目录并运行以下代码开始:

npm i fastify-gql graphql

The above installs GraphQL and the Fastify barebone GraphQL adapter.

上面安装了GraphQLFastify准系统GraphQL适配器。

Navigate to the src directory and run the following code:

导航到src目录并运行以下代码:

mkdir schema
cd shema
touch index.js

Navigate to the src directory update the index.js file with the following:

导航到src目录,使用以下命令更新index.js文件:

// Import Server
const fastify = require('./server.js')

// Import external dependancies
const gql = require('fastify-gql')

// Import GraphQL Schema
const schema = require('./schema')

// Register Fastify GraphQL
fastify.register(gql, {
   schema,
   graphiql: true
})

... end here

// Import Routes
const routes = require('./routes')

With the above code we require the Fastify GraphQL Adapter, import the schema and register the GraphQl Adapter with Fastify.

使用上面的代码,我们需要Fastify GraphQL Adapter,导入架构并向Fastify注册GraphQl Adapter

We register the schema and enable GraphiQL, an in-browser IDE for exploring GraphQL.

我们注册模式并启用GraphiQL,这是一个用于浏览GraphQL的浏览器内置 IDE

Navigate to the schema directory and open the index.js file and add the following boilerplate code:

导航到schema目录并打开index.js文件,并添加以下样板代码:

Let’s run through the above code:

让我们来看一下上面的代码:

We require the main GraphQL package and use JavaScript Destructuring to get the necessary GraphQL functions(GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLID, GraphQLList and GraphQLNonNull).

我们需要主要的GraphQL软件包,并使用JavaScript 分解来获取必要的GraphQL函数( GraphQLSchemaGraphQLObjectTypeGraphQLStringGraphQLIntGraphQLIDGraphQLListGraphQLNonNull )。

We import our three controllers (carController, ownerController and serviceController).

我们导入三个controllers ( carControllerownerControllerserviceController )。

We declare the carType, ownerType and serviceType GraphQL Object Types, which are functions that accept an object as a parameter, with a name and a fields key.

我们声明carTypeownerTypeserviceType GraphQL对象类型 这是接受一个对象作为参数,用函数namefields的关键。

These functions are used to define our GraphQL schema, similar to the Mongoose models defined earlier.

这些函数用于定义我们的GraphQL模式,类似于之前定义的Mongoose模型。

The fields can return a particular type, and methods that take arguments. Learn More about Object Types.

字段可以返回特定的type以及带有参数的方法了解有关对象类型的更多信息

Then we declare the RootQuery which is also a GraphQL Object Type and is found at the top level of every GraphQL server. It represents all of the possible entry points into the GraphQL API. Learn More about root fields and resolvers.

然后,我们声明RootQuery ,它也是GraphQL对象类型 ,位于每个GraphQL服务器的顶层。 它代表了GraphQL API的所有可能的入口点 了解有关根字段和解析程序的更多信息

We then declare our Mutations, which are used to change data. Although any query could be implemented to change data, operations that cause changes should be sent explicitly via a mutation. Learn More about Mutations.

然后,我们声明我们的Mutations ,用于更改数据。 尽管可以实施任何查询来更改数据,但是引起更改的操作应通过变体显式发送。 了解有关突变的更多信息

Lastly we export the GraphQLSchema.

最后,我们导出GraphQLSchema.

Now that we have our template setup we can start populating the Object Types, Root Query and Mutations.

现在我们有了模板设置,我们可以开始填充对象类型根查询突变了

Note that there are Mongoose to GraphQL schema generators available, but for the tutorial purposes we will manually create the schema.

请注意,有可用的Mongoose到GraphQL模式生成器,但是出于教程目的,我们将手动创建模式。

Let’s update the carType Object Type as follows:

让我们更新carType 对象类型 ,如下所示:

Let’s dive deeper into the GraphQL functions, starting with the Scalars types in GraphQL:

让我们深入探讨的GraphQL功能,首先是标量类型GraphQL:

GraphQL comes with a set of default scalar types out of the box:

GraphQL提供了一组默认的标量类型:

  • Int: A signed 32‐bit integer. GraphQLInt

    Int :有符号的32位整数。 GraphQLInt

  • Float: A signed double-precision floating-point value. GraphQLFloat

    Float :带符号的双精度浮点值。 GraphQLFloat

  • String: A UTF‐8 character sequence. GraphQLString

    String :UTF-8字符序列。 GraphQLString

  • Boolean: true or false. GraphQLBoolean

    BooleantruefalseGraphQLBoolean

  • ID: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialised in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable. GraphQLID

    ID :ID标量类型表示唯一的标识符,通常用于重新获取对象或用作缓存的键。 ID类型的序列化方法与String相同; 但是,将其定义为ID表示它不是人类可读的。 GraphQLID

The owner and service fields are where it gets interesting. These fields are not defined as Scalar types like the rest — instead, their type is referencing the ownerType and serviceType that we have created and are yet to populate.

ownerservice领域很有趣。 这些字段没有像其他字段那样定义为标量类型 -而是,它们的type引用了我们已经创建但尚未填充的ownerTypeserviceType

The second argument that we pass into the owner and service fields are resolver functions.

我们传递给ownerservice字段的第二个参数是解析程序功能。

Resolver functions or methods are functions that resolves a value for a type or field in a schema

解析器函数或方法是为架构中的类型或字段解析值的函数

Resolvers can be asynchronous too! They can resolve values from another REST API, database, cache, constant, etc.

解析器也可以是异步的! 他们可以从另一个REST API,数据库,缓存,常量等解析值

You can think of each field in a GraphQL query as a function or method of the previous type which returns the next type. In fact, this is exactly how GraphQL works. Each field on each type is backed by a function called the resolver which is provided by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value.

您可以将GraphQL查询中的每个字段视为返回下一个类型的上一个类型的函数或方法。 实际上,这正是GraphQL的工作方式。 每种类型的每个字段均由GraphQL服务器开发人员提供的称为解析器的功能支持。 执行字段时,将调用相应的解析器以生成下一个值。

You can think of each field in a GraphQL query as a function or method of the previous type which returns the next type. In fact, this is exactly how GraphQL works. Each field on each type is backed by a function called the resolver which is provided by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value.

您可以将GraphQL查询中的每个字段视为返回下一个类型的上一个类型的函数或方法。 实际上,这正是GraphQL的工作方式。 每种类型的每个字段都由GraphQL服务器开发人员提供的称为解析器的功能支持。 执行字段时,将调用相应的解析器以生成下一个值。

In order to create the relationship between the different types we pass the _id and the owner_id values into the respective controller functions.

为了创建不同类型之间的关系,我们将_idowner_id值传递到相应的控制器函数中。

So essentially we are requesting the owner details along with the car details:

因此,本质上,我们要求所有者详细信息以及汽车详细信息:

return await userController.getSingleOwner({ id: parent.owner_id })

and the details of all the services related to the car:

以及与汽车有关的所有服务的详细信息:

return await serviceController.getCarsServices({ id: parent._id })

To return a list or array from with GraphQL, we use the GraphQLList. Here is a great in depth tutorial about using arrays in GraphQL Schema, but it is really simple: whenever we need an array we will use the GraphQLList function.

要使用GraphQL返回列表或数组我们使用GraphQLList是一本关于在GraphQL模式中使用数组的深入教程,但它确实很简单:每当我们需要数组时,我们都将使用GraphQLList函数。

Let’s update the ownerType and serviceType with the following code:

让我们使用以下代码更新ownerTypeserviceType

ownerType

ownerType

serviceType

serviceType

The above two Object Types are very similar to the carType. You can notice a pattern between the different Object Types and their relationships.

上面的两个对象类型carType非常相似。 您会注意到不同对象类型及其关系之间的模式。

We can now populate the RootQuery root with the following code:

现在,我们可以使用以下代码填充RootQuery根目录:

There are no new concepts in the above code, but keep in mind that the RootQuery query is the entry point to all queries on the GraphQL API. So from the above we can see that we can run the following queries directly:

上面的代码中没有新概念,但是请记住, RootQuery查询是GraphQL API上所有查询的入口点 因此,从上面我们可以看到我们可以直接运行以下查询:

  • Get all the Cars

    取得所有汽车
  • Get a single Car

    一辆车
  • Get a single Owner

    获得一个所有者
  • Get a single Service

    获得单一服务

Let’s open the GraphiQL user interface and build some queries: http://localhost:3000/graphiql.html

让我们打开GraphiQL用户界面并构建一些查询: http:// localhost:3000 / graphiql.html

Queries are entered on the left, results are in the middle, and the documentation explorer is on the right.

查询在左侧输入,结果在中间,文档浏览器在右侧。

The documentation explorer can be used to explore the entire graph down to Scalar level. This is very helpful when building queries.

文档浏览器可用于浏览整个图形直至标量级别。 这在构建查询时非常有帮助。

The language used to build the queries resembles JSON. This cheat sheet is a great a reference.

用于构建查询的语言类似于JSON。 该备忘单是一个很好的参考。

Below demonstrates why GraphQL is so awesome:

下面展示了GraphQL为何如此出色

In the above example, we are using the cars root query to display a list of all the cars, their owners, and their services.

在上面的示例中,我们使用cars根查询来显示所有汽车,其拥有者及其服务的列表。

We have one final topic to address, and that is mutations. Let’s update the mutations with the following code:

我们有最后一个主题要解决,那就是mutations 。 让我们使用以下代码更新mutations

As before, we declare our Object Type, specify the name and the fields.

和以前一样,我们声明对象类型 ,指定名称字段

A mutation consists of the the type, args and the async resolve function. The resolve function passes the args to the controller, which returns the result of the mutation.

突变由typeargs异步解析函数组成。 resolve函数将args传递给控制器​​,该控制器返回突变的结果。

You have now coded a fully functional REST API and a fully functional GraphQL API.

现在,您已经编码了功能齐全的REST API和功能齐全的GraphQL API。

There are no rules stating that one should use exclusively REST or exclusively GraphQL. In some projects, the best solution may be a mix of both. This is really determined on a project-to-project basis.

没有规则说明应该只使用REST或只使用GraphQL。 在某些项目中,最佳解决方案可能是两者兼而有之。 这实际上是根据项目而定的。

You can download the source code form Git here.

您可以在此处下载源代码形式的Git。

接下来是什么? (What is Next?)

In the next tutorial, we will consume our GraphQL API with a Vue.js frontend as a single page application!

在下一个教程中,我们将使用带有Vue.js前端的GraphQL API作为单页应用程序!

翻译自: https://www.freecodecamp.org/news/how-to-build-a-blazing-fast-graphql-api-with-node-js-mongodb-and-fastify-77fd5acd2998/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值