node graphql_带有Node.js和Express的实用GraphQL入门指南

node graphql

介绍 (Introduction)

GraphQL is a query language created by Facebook with the purpose of building client applications based on intuitive and flexible syntax for describing their data requirements and interactions. A GraphQL service is created by defining types and fields on those types, then providing functions for each field on each type.

GraphQL是Facebook创建的一种查询语言,旨在基于直观和灵活的语法构建客户端应用程序,以描述其数据需求和交互。 通过定义类型和类型上的字段,然后为每种类型上的每个字段提供功能,来创建GraphQL服务。

Once a GraphQL service is running (typically at a URL on a web service), it can receive GraphQL queries to validate and execute. A received query is first checked to ensure it only refers to the types and fields defined, then runs the provided functions to produce a result.

GraphQL服务运行之后(通常在Web服务上的URL),它可以接收GraphQL查询以进行验证和执行。 首先检查收到的查询,以确保它仅引用定义的类型和字段,然后运行提供的函数以产生结果。

In this tutorial, we are going to implement a GraphQL server using Express and use it to learn important GraphQL features.

在本教程中,我们将使用Express来实现GraphQL服务器,并使用它来学习重要的GraphQL功能。

Some of GraphQL features include:

GraphQL的一些功能包括:

  • Hierarchical - Queries look exactly like the data they return.

    分层-查询看起来与返回的数据完全一样。

  • Client-specified queries - The client has the liberty to dictate what to fetch from the server.

    客户端指定的查询-客户端可以自由决定从服务器获取什么。

  • Strongly typed - You can validate a query syntactically and within the GraphQL type system before execution. This also helps leverage powerful tools that improve the development experience, such as GraphiQL.

    强类型-执行之前,您可以在GraphQL类型系统中在语法上验证查询。 这也有助于利用功能强大的工具来改善开发体验,例如GraphiQL。

  • Introspective - You can query the type system using the GraphQL syntax itself. This is great for parsing incoming data into strongly-typed interfaces, and not having to deal with parsing and manually transforming JSON into objects.

    内省-您可以使用GraphQL语法本身来查询类型系统。 这非常适合将传入的数据解析为强类型的接口,而不必处理解析并将JSON手动转换为对象的情况。

目标 (Goals)

One of the primary challenges with traditional REST calls is the inability of the client to request a customized (limited or expanded) set of data. In most cases, once the client requests information from the server, it either gets all or none of the fields.

传统REST调用的主要挑战之一是客户端无法请求自定义(受限或扩展)数据集。 在大多数情况下,一旦客户端从服务器请求信息,它要么获取所有字段,要么不获取任何字段。

Another difficulty is working and maintaining multiple endpoints. As a platform grows, consequently the number will increase. Therefore, clients often need to ask for data from different endpoints. GraphQL APIs are organized in terms of types and fields, not endpoints. You can access the full capabilities of your data from a single endpoint.

另一个困难是工作和维护多个端点。 随着平台的发展,因此数量会增加。 因此,客户经常需要从不同的端点请求数据。 GraphQL API按照类型和字段(而不是端点)进行组织。 您可以从单个端点访问数据的全部功能。

When building a GraphQL server, it is only necessary to have one URL for all data fetching and mutating. Thus, a client can request a set of data by sending a query string, describing what they want, to a server.

构建GraphQL服务器时,仅需要为所有数据获取和变异使用一个URL。 因此,客户端可以通过向服务器发送描述他们想要的查询字符串来请求一组数据。

先决条件 (Prerequisites)

第1步—使用节点设置GraphQL (Step 1 — Setting Up GraphQL with Node)

You’ll start by creating a basic file structure and a sample code snippet.

您将从创建一个基本的文件结构和一个示例代码片段开始。

First create a GraphQL directory:

首先创建一个GraphQL目录:

  • mkdir GraphQL

    mkdir GraphQL

Change into the new directory:

转到新目录:

  • cd GraphQL

    cd GraphQL

Initialize a npm project:

初始化一个npm项目:

  • npm init -y

    npm初始化-y

Then create the server.js file which will be the main file:

然后创建server.js文件,它将是主文件:

  • touch server.js

    触摸server.js

Your project should resemble the following:

您的项目应类似于以下内容:

Necessary packages will be discussed in this tutorial as they are implemented. Next, set up a server using Express and express-graphql, an HTTP server middleware:

必要的程序包将在实现时进行讨论。 接下来,使用Expressexpress-graphql (HTTP服务器中间件)设置服务器:

  • npm i graphql express express-graphql -S

    npm我graphql表达快递-graphql -S

Open server.js in a text editor and add the following lines of code:

在文本编辑器中打开server.js并添加以下代码行:

server.js
server.js
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');

// Initialize a GraphQL schema
var schema = buildSchema(`
  type Query {
    hello: String
  }
`);

// Root resolver
var root = { 
  hello: () => 'Hello world!'
};

// Create an express server and a GraphQL endpoint
var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,  // Must be provided
  rootValue: root,
  graphiql: true,  // Enable GraphiQL when server endpoint is accessed in browser
}));
app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'));

This snippet accomplishes several things. It uses require to include the packages that were installed. It also initializes generic schema and root values. Furthermore, it creates an endpoint at /graphql which can be visited with a web browser.

该代码片段完成了几件事。 它使用require来包含已安装的软件包。 它还会初始化通用schemaroot值。 此外,它在/graphql处创建一个端点,可以通过Web浏览器进行访问。

Save and close the file after making these changes.

进行这些更改后,保存并关闭文件。

Start the Node server if it is not running:

如果节点服务器未运行,请启动它:

  • node server.js

    节点server.js

Note: Throughout this tutorial you will be making updates to server.js that will require restarting the node server to reflect the latest changes.

注意:在本教程中,您将对server.js进行更新,这将需要重新启动节点服务器以反映最新更改。

Visit localhost:4000/graphql in a web browser. You will see a Welcome to GraphiQL web interface.

在网络浏览器中访问localhost:4000/graphql 。 您将看到“ 欢迎使用GraphiQL” Web界面。

There will be a pane on the left where you will be entering queries. There is an additional pane for entering query variables that you may need to drag and resize to view. The pane on the right will display the results of executing your queries. Furthermore, executing queries can be accomplished by pressing the button with the play icon.

左侧将有一个窗格,您将在其中输入查询。 还有一个用于输入查询变量的窗格,您可能需要拖动该窗格并调整其大小才能查看。 右侧窗格将显示执行查询的结果。 此外,可以通过按带有播放图标的按钮来完成执行查询。

So far we have explored some features and advantages of GraphQL. In this next section, we’ll delve into different terminologies and implementations of some technical features in GraphQL. We’ll be using an Express server to practice these features.

到目前为止,我们已经探究了GraphQL的一些功能和优点。 在下一节中,我们将研究GraphQL中一些技术功能的不同术语和实现。 我们将使用Express服务器来练习这些功能。

第2步-定义架构 (Step 2 — Defining a Schema)

In GraphQL, the Schema manages queries and mutations, defining what is allowed to be executed in the GraphQL server. A schema defines a GraphQL API’s type system. It describes the complete set of possible data (objects, fields, relationships, etc.) that a client can access. Calls from the client are validated and executed against the schema. A client can find information about the schema via introspection. A schema resides on the GraphQL API server.

在GraphQL中,该模式管理查询和变异,定义允许在GraphQL服务器中执行的内容。 模式定义GraphQL API的类型系统。 它描述了客户端可以访问的完整的可能数据集(对象,字段,关系等)。 来自客户端的调用将根据架构进行验证并执行。 客户可以通过自省找到有关架构的信息。 模式驻留在GraphQL API服务器上。

GraphQL Interface Definition Language (IDL) or Schema Definition Language (SDL) are the most concise ways to specify a GraphQL Schema. The most basic components of a GraphQL schema are object types, which represent a kind of object we can fetch from our service, and what fields it has.

GraphQL接口定义语言(IDL)或架构定义语言(SDL)是指定GraphQL架构的最简洁的方法。 GraphQL模式的最基本组成部分是对象类型,它表示我们可以从服务中获取的一种对象以及它具有的字段。

In the GraphQL schema language, you might represent a user with an id, name, and age like this example:

在GraphQL模式语言中,您可以使用idnameage表示user ,如下例所示:

type User {
  id: ID!
  name: String!
  age: Int
}

In JavaScript, you would use the buildSchema function which builds a Schema object from GraphQL schema language. If you were to represent the same user above, it would look like this example:

在JavaScript中,您将使用buildSchema函数,该函数从GraphQL模式语言构建Schema对象。 如果要在上面代表同一user ,则该示例如下所示:

var schema = buildSchema(`
  type User {
    id: Int
    name: String!
    age: Int
  }
`);

构造类型 (Constructing Types)

You can define different types inside buildSchema, which you might notice in most cases are type Query {...} and type Mutation {...}. type Query {...} is an object holding the functions that will be mapped to GraphQL queries, used to fetch data (equivalent to GET in REST). type Mutation {...} holds functions that will be mapped to mutations, used to create, update, or delete data (equivalent to POST, UPDATE, and DELETE in REST).

您可以在buildSchema定义不同的类型,在大多数情况下您可能会注意到type Query {...}type Mutation {...}type Query {...}是一个对象,其中包含将被映射到GraphQL查询的函数,该函数用于获取数据(相当于REST中的GET)。 type Mutation {...}保存将映射到突变的函数,这些函数用于创建,更新或删除数据(相当于REST中的POST,UPDATE和DELETE)。

You’ll make your schema a bit complex by adding some reasonable types. For instance, you want to return a user and an array of users of type Person, who have an id, name ,age, and their favorite shark properties.

通过添加一些合理的类型,您将使您的架构有些复杂。 例如,您想返回一个user和一个类型为Personusers数组,它们具有idnameage和他们最喜欢的shark属性。

Replace the pre-existing lines of code for schema in server.js with this new Schema object:

用以下新的Schema对象替换server.js schema的现有代码行:

server.js
server.js
// Initialize a GraphQL schema
var schema = buildSchema(`
  type Query {
    user(id: Int!): Person
    users(shark: String): [Person]
  },
  type Person {
    id: Int
    name: String
    age: Int
    shark: String
  }
`);

You may notice some interesting syntax above, [Person] means return an array of type Person while the exclamation in user(id: Int!) means that the id must be provided. users query takes an optional shark variable.

您可能会注意到上面一些有趣的语法, [Person]表示返回类型为Person的数组,而user(id: Int!)的感叹号表示必须提供idusers查询采用可选的shark变量。

第3步-定义解析器 (Step 3 — Defining Resolvers)

A resolver is responsible for mapping the operation to an actual function. Inside type Query, you have an operation called users. You map this operation to a function with the same name inside root.

解析器负责将操作映射到实际功能。 在type Query ,您有一个名为users的操作。 您将此操作映射到root内具有相同名称的函数。

You’ll also create some sample users for this functionality.

您还将为此功能创建一些示例用户。

Add these new lines of code to server.js right after the buildSchema lines of code, but before the root lines of code:

将这些新的代码行添加到server.jsbuildSchemabuildSchema的代码行之后,但在代码的root行之前:

server.js
server.js
...
// Sample users
var users = [
  {
    id: 1,
    name: 'Brian',
    age: '21',
    shark: 'Great White Shark'
  },
  {
    id: 2,
    name: 'Kim',
    age: '22',
    shark: 'Whale Shark'
  },
  {
    id: 3,
    name: 'Faith',
    age: '23',
    shark: 'Hammerhead Shark'
  },
  {
    id: 4,
    name: 'Joseph',
    age: '23',
    shark: 'Tiger Shark'
  },
  {
    id: 5,
    name: 'Joy',
    age: '25',
    shark: 'Hammerhead Shark'
  }
];

// Return a single user
var getUser = function(args) {
  // ...
}

// Return a list of users
var retrieveUsers = function(args) { 
  // ...
}
...

Replace the pre-existing lines of code for root in server.js with this new object:

用以下新对象替换server.js root的现有代码行:

server.js
server.js
// Root resolver
var root = { 
  user: getUser,  // Resolver function to return user with specific id
  users: retrieveUsers
};

To make the code more readable, create separate functions instead of piling everything in the root resolver. Both functions take an optional args parameter which carries variables from the client query. Let’s provide an implementation for the resolvers and test their functionality.

为了使代码更具可读性,请创建单独的函数,而不是将所有内容堆积在根解析器中。 这两个函数都带有一个可选的args参数,该参数带有来自客户端查询的变量。 让我们为解析器提供一个实现并测试其功能。

Replace the lines of code for getUser and retrieveUsers that you added earlier to server.js with the following:

替换的代码行getUserretrieveUsers你要添加的前面server.js下列要求:

server.js
server.js
// Return a single user (based on id)
var getUser = function(args) {
  var userID = args.id;
  return users.filter(user => user.id == userID)[0];
}

// Return a list of users (takes an optional shark parameter)
var retrieveUsers = function(args) {
  if (args.shark) {
    var shark = args.shark;
    return users.filter(user => user.shark === shark);
  } else {
    return users;
  }
}

In the web interface, enter the following query in the input pane:

在Web界面中,在输入窗格中输入以下查询:

query getSingleUser {
  user {
    name
    age
    shark
  }
}

You will receive the following output:

您将收到以下输出:


   
   
Output
{ "errors": [ { "message": "Cannot query field \"user\" on type \"Query\".", "locations": [ { "line": 2, "column": 3 } ] } ] }

In the example above, we are using an operation named getSingleUser to get a single user with their name, age, and favorite shark. We could optionally specify that we need their name only if we did not need the age and shark.

在上面的示例中,我们使用名为getSingleUser的操作来获取具有其nameage和最喜欢的shark的单个用户。 我们可以选择指定仅在不需要ageshark才需要它们的name

According to the official documentation, it is easiest to identify queries in your codebase by name instead of by deciphering the contents.

根据官方文档 ,最简单的是通过名称而不是通过解密内容来识别代码库中的查询。

This query does not provide the required id and GraphQL gives us a descriptive error message. We’ll now make a correct query. Take notice of the use of variables and arguments.

该查询未提供所需的id并且GraphQL给我们提供了描述性错误消息。 现在,我们将进行正确的查询。 注意变量和参数的使用。

In the web interface, replace the content of the input pane with the following corrected query:

在Web界面中,使用以下更正的查询替换输入窗格的内容:

query getSingleUser($userID: Int!) {
  user(id: $userID) {
    name
    age
    shark
  }
}

While still in the web interface, replace the content of the variables pane with the following:

仍在Web界面中时,将变量窗格的内容替换为以下内容:


   
   
Query Variables
{ "userID": 1 }

You will receive the following output:

您将收到以下输出:


   
   
Output
{ "data": { "user": { "name": "Brian", "age": 21, "shark": "Great White Shark" } } }

This returns a single user matching the id of 1, Brian. It also returns the requested name, age, and shark fields.

这将返回与id 1 Brian匹配的单个用户。 它还返回请求的nameageshark字段。

第4步-定义别名 (Step 4 — Defining Aliases)

In a situation where you need to retrieve two different users, you may be wondering how you would identify each user. In GraphQL, you can’t directly query for the same field with different arguments. Let’s demonstrate this.

在需要检索两个不同用户的情况下,您可能想知道如何识别每个用户。 在GraphQL中,您不能直接使用不同的参数查询同一字段。 让我们演示一下。

In the web interface, replace the content of the input pane with the following:

在Web界面中,将输入窗格的内容替换为以下内容:

query getUsersWithAliasesError($userAID: Int!, $userBID: Int!) {
  user(id: $userAID) {
    name
    age
    shark
  },
  user(id: $userBID) {
    name
    age
    shark
  }
}

While still in the web interface, replace the content of the variables pane with the following:

仍在Web界面中时,将变量窗格的内容替换为以下内容:


   
   
Query Variables
{ "userAID": 1, "userBID": 2 }

You will receive the following output:

您将收到以下输出:


   
   
Output
{ "errors": [ { "message": "Fields \"user\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.", "locations": [ { "line": 2, "column": 3 }, { "line": 7, "column": 3 } ] } ] }

The error is descriptive and even suggests the use of aliases. Let’s correct the implementation.

该错误是描述性的,甚至建议使用别名。 让我们更正实现。

In the web interface, replace the content of the input pane with the following corrected query:

在Web界面中,使用以下更正的查询替换输入窗格的内容:

query getUsersWithAliases($userAID: Int!, $userBID: Int!) {
  userA: user(id: $userAID) {
    name
    age
    shark
  },
  userB: user(id: $userBID) {
    name
    age
    shark
  }
}

While still in the web interface, ensure the variables pane contains the following:

仍在Web界面中时,请确保变量窗格包含以下内容:


   
   
Query Variables
{ "userAID": 1, "userBID": 2 }

You will receive the following output:

您将收到以下输出:


   
   
Output
{ "data": { "userA": { "name": "Brian", "age": 21, "shark": "Great White Shark" }, "userB": { "name": "Kim", "age": 22, "shark": "Whale Shark" } } }

Now we can correctly identify each user with their fields.

现在,我们可以正确识别每个用户及其字段。

第5步-创建片段 (Step 5 — Creating Fragments)

The query above is not that bad, but it has one problem; we are repeating the same fields for both userA and userB. We could find something that will make our queries DRY. GraphQL includes reusable units called fragments that let you construct sets of fields, and then include them in queries where you need to.

上面的查询还不错,但是有一个问题。 我们对userAuserB重复相同的字段。 我们可以找到使查询变得干燥的东西。 GraphQL包含称为片段的可重用单元,可让您构造字段集,然后将它们包含在需要的查询中。

In the web interface, replace the content of the variables pane with the following:

在Web界面中,将变量窗格的内容替换为以下内容:

query getUsersWithFragments($userAID: Int!, $userBID: Int!) {
  userA: user(id: $userAID) {
    ...userFields
  },
  userB: user(id: $userBID) {
    ...userFields
  }
}

fragment userFields on Person {
  name
  age
  shark
}

While still in the web interface, ensure the variables pane contains the following:

仍在Web界面中时,请确保变量窗格包含以下内容:


   
   
Query Variables
{ "userAID": 1, "userBID": 2 }

You will receive the following output:

您将收到以下输出:


   
   
Output
{ "data": { "userA": { "name": "Brian", "age": 21, "shark": "Great White Shark" }, "userB": { "name": "Kim", "age": 22, "shark": "Whale Shark" } } }

You have created a fragment called userFields that can only be applied on type Person and then used it to retrieve users.

您已经创建了一个名为userFields的片段,该片段只能应用于type Person ,然后使用它来检索用户。

第6步-定义指令 (Step 6 — Defining Directives)

Directives enable us to dynamically change the structure and shape of our queries using variables. At some point you might want to skip or include some fields without altering the schema. The two available directives are as follows:

指令使我们能够使用变量动态更改查询的结构和形状。 在某些时候,您可能希望跳过或包括某些字段而不更改架构。 两个可用的指令如下:

  • @include(if: Boolean)- Only include this field in the result if the argument is true.

    @include(if: Boolean) -如果参数为true,则仅在结果中包括此字段。

  • @skip(if: Boolean)- Skip this field if the argument is true.

    @skip(if: Boolean) -如果参数为true,则跳过此字段。

Say you want to retrieve users that are fans of Hammerhead Shark, but include their id and skip their age fields. You can use variables to pass in the shark and use directives for the inclusion and skipping functionalities.

假设您要检索的是Hammerhead Shark ,但要提供其id并跳过其age字段。 您可以使用变量来传递shark并使用指令来包含和跳过功能。

In the web interface, clear the input pane and add the following:

在Web界面中,清除输入窗格并添加以下内容:

query getUsers($shark: String, $age: Boolean!, $id: Boolean!) {
  users(shark: $shark){
    ...userFields
  }
}

fragment userFields on Person {
  name
  age @skip(if: $age)
  id @include(if: $id)
}

While still in the web interface, clear the variables pane and add the following:

仍在Web界面中时,清除变量窗格并添加以下内容:


   
   
Query Variables
{ "shark": "Hammerhead Shark", "age": true, "id": true }

You will receive the following output:

您将收到以下输出:


   
   
Output
{ "data": { "users": [ { "name": "Faith", "id": 3 }, { "name": "Joy", "id": 5 } ] } }

This returns two users with shark values matching Hammerhead SharkFaith and Joy.

这将返回两个shark值与Hammerhead SharkFaithJoy匹配的用户。

第7步-定义突变 (Step 7 — Defining Mutations)

So far we have been dealing with queries, the operations to retrieve data. Mutations are the second main operation in GraphQL which deals with creating, deleting, and updating data.

到目前为止,我们一直在处理查询,即检索数据的操作。 突变是GraphQL中的第二个主要操作,它处理创建,删除和更新数据。

Let’s focus on some examples of how to carry out mutations. For instance, we want to update a user with id == 1 and change their age, name, and then return the new user details.

让我们集中于一些如何进行突变的例子。 例如,我们要更新id == 1的用户并更改其agename ,然后返回新的用户详细信息。

Update your schema to include a mutation type in addition to the pre-existing lines of code:

更新您的架构,以在现有代码行之外添加一个突变类型:

server.js
server.js
// Initialize a GraphQL schema
var schema = buildSchema(`
  type Query {
    user(id: Int!): Person
    users(shark: String): [Person]
  },
  type Person {
    id: Int
    name: String
    age: Int
    shark: String
  }
  # newly added code
  type Mutation {
    updateUser(id: Int!, name: String!, age: String): Person
  }
`);

After getUser and retrieveUsers, add a new updateUser function to handle updating a user:

getUserretrieveUsers ,添加一个新的updateUser函数来处理更新用户:

server.js
server.js
// Update a user and return new user details
var updateUser = function({id, name, age}) {
  users.map(user => {
    if (user.id === id) {
      user.name = name;
      user.age = age;
      return user;
    }
  });
  return users.filter(user => user.id === id)[0];
}

Also update the root resolver with relevant resolver functions:

还使用相关的解析器功能更新根解析器:

server.js
server.js
// Root resolver
var root = { 
  user: getUser,
  users: retrieveUsers,
  updateUser: updateUser  // Include mutation function in root resolver
};

Assuming these are the initial user details:

假设这些是初始用户详细信息:


   
   
Output
{ "data": { "user": { "name": "Brian", "age": 21, "shark": "Great White Shark" } } }

In the web interface, add the following query to the input pane:

在Web界面中,将以下查询添加到输入窗格:

mutation updateUser($id: Int!, $name: String!, $age: String) {
  updateUser(id: $id, name:$name, age: $age){
    ...userFields
  }
}

fragment userFields on Person {
  name
  age
  shark
}

While still in the web interface, clear the variables pane and add the following:

仍在Web界面中时,清除变量窗格并添加以下内容:


   
   
Query Variables
{ "id": 1, "name": "Keavin", "age": "27" }

You will receive the following output:

您将收到以下输出:


   
   
Output
{ "data": { "updateUser": { "name": "Keavin", "age": 27, "shark": "Great White Shark" } } }

After a mutation to update the user, you get the new user details.

进行更新以更新用户后,您将获得新的用户详细信息。

The user with the id of 1 has been updated from Brian (age 21) to Keavin (age 27).

id1的用户已从Brian ( age 21 )更新为Keavin ( age 27 )。

结论 (Conclusion)

In this guide, you have covered basic concepts of GraphQL to some fairly complex examples. Most of these examples reveal the differences between GraphQL and REST for users who have interacted with REST.

在本指南中,您已经通过一些相当复杂的示例涵盖了GraphQL的基本概念。 这些示例大多数都揭示了与REST交互的用户GraphQL和REST之间的区别。

To learn more about GraphQL, check the official documentation.

要了解有关GraphQL的更多信息,请查看官方文档

翻译自: https://www.digitalocean.com/community/tutorials/a-practical-graphql-getting-started-guide-with-nodejs

node graphql

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值