node graphql_Node.js实用GraphQL入门指南

node graphql

Most of us may be very familiar with creating REST APIs. 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. GraphQL was designed to solve one of the biggest drawbacks of REST-like APIs. A GraphQL service is created by defining types and fields on those types, then providing functions for each field on each type.

我们大多数人可能对创建REST API非常熟悉。 GraphQL是一种查询语言,由Facebook创建,目的是基于直观而灵活的语法构建客户端应用程序,以描述其数据需求和交互。 GraphQL旨在解决类似REST的API的最大缺点之一。 通过定义类型和类型上的字段,然后为每种类型上的每个字段提供功能,来创建GraphQL服务。

Once a GraphQL service is running (typically at a URL on a web service), it can be sent 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 very important GraphQL features.

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

GraphQL功能 ( GraphQL Features )

  • 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手动转换为对象的情况。

为什么要使用GraphQL ( Why use GraphQL )

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 maintain 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即可进行所有数据的获取和变异。 因此,客户端可以通过向服务器发送描述他们想要的查询字符串来请求一组数据。

建立 ( Setup )

We will begin with a simple file structure and a simple sample code snippet. To follow this tutorial step by step, create a GraphQL folder and inside the folder initialize an npm project using npm init -y. Then create a file server.js which will be the main file. For complete code, clone the repository using the instructions in the attached github repository.

我们将从一个简单的文件结构和一个简单的示例代码片段开始。 要逐步遵循本教程,请创建一个GraphQL文件夹,并在该文件夹内使用npm init -y初始化一个npm项目。 然后创建一个文件server.js ,它将成为主文件。 有关完整的代码,请使用随附的github存储库中的说明克隆存储库。

We will install the packages required which will be discussed at each step.

我们将安装所需的软件包,将在每个步骤中进行讨论。

npm i graphql express express-graphql -S   #Install required packages

We will setup a simple server using Express and express-graphql, a HTTP server middleware. Do not worry about the meaning of the code as we will explore it bit by bit in the next steps while adding more content.

我们将使用Expressexpress-graphql (一个HTTP服务器中间件)设置一个简单的服务器。 不必担心代码的含义,因为我们将在后续步骤中逐步探索它,同时添加更多内容。

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

概念 ( Concepts )

So far we have explored some features and advantages of GraphQL. In the this section we will delve into different teminologies and implementations of some technical features in GraphQL. We will be using a simple Express server to paractice our features.

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

架构图 (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, everything) 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) is the most concise way to specify a GraphQL Schema. The most basic components of a GraphQL schema are object types, which just represent a kind of object you can fetch from your service, and what fields it has. In the GraphQL schema language, we might represent it like this:

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

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

In the above snippet we are using buildSchema function which builds a Schema object from GraphQL schema language.

在上面的代码片段中,我们使用buildSchema函数,该函数从GraphQL模式语言构建一个Schema对象。

var schema = buildSchema(`
  type Query {
    hello: String
  }
`);
构造类型 (Constructing Types)

Inside buildSchema, we can define different types. You might notice in most cases 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) while type Mutation {...} holds functions that will be mapped to mutaions; 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 {...}包含将映射到mutaions的函数; 用于创建,更新或删除数据(相当于REST中的POST,UPDATE和DELETE)。

We will make our schema a bit complex by adding some reasonable types. For instance, we want to return a user and an array of users of type Person who has an id, name ,age and gender properties.

我们将通过添加一些合理的类型来使我们的模式有些复杂。 举例来说,我们要返回user和数组users类型的Person谁拥有一个idnameagegender属性。

var schema = buildSchema(`
  type Query {
    user(id: Int!): Person
    users(gender: String): [Person]
  },
  type Person {
    id: Int
    name: String
    age: Int
    gender: String    
  }
`);

You can 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 gender variable.

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

解析器 (Resolver)

A resolver is responsible for mapping the operation to actual function. Inside type Query, we have an operation called users. We map this operation to a function with the same name inside root. We will also create some dummy users for this functionality.

解析器负责将操作映射到实际功能。 在type Query内部,我们有一个名为users的操作。 我们将此操作映射到root内具有相同名称的函数。 我们还将为此功能创建一些虚拟用户。

...
var users = [  // Dummy data
  {
    id: 1,
    name: 'Brian',
    age: '21',
    gender: 'M'
  },
  {
    id:2,
    name: 'Kim',
    age: '22',
    gender: 'M'
  },
  {
    id:3,
    name: 'Joseph',
    age: '23',
    gender: 'M'
  },
  {
    id:3,
    name: 'Faith',
    age: '23',
    gender: 'F'
  },
  {
    id:5,
    name: 'Joy',
    age: '25',
    gender: 'F'
  }
];

var getUser = function(args) { ... }  // Return a single user
var retrieveUsers = function(args) { ... } // Return  a list of users
...
var root = { 
  user: getUser,   // Resolver function to return user with specific id
  users: retrieveUsers
};

To make the code more readable, we will 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 implementaion for the resolvers and test their functionality.

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

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

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

Query

询问

query getSingleUser {
    user {
        name
        age
        gender
    }
}

Output

输出量

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

在上图中,我们使用操作名称getSingleUser来获取具有其名称,性别和年龄的单个用户。 我们可以选择指定仅当我们不需要年龄和性别时才需要他们的名字。

When something goes wrong either in your network logs or your GraphQL server, it is easier to identify a query in your codebase by name instead of trying to decipher the contents.

当您的网络日志或GraphQL服务器出现问题时,通过名称识别代码库中的查询比尝试解密内容更容易。

This query does not provide the required id and GraphQL gives us a very descriptive error message. We will now make a correct query, notice the use of variables/arguments.

该查询不提供所需的id并且GraphQL给我们一个非常描述性的错误消息。 现在,我们将进行正确的查询,请注意变量/参数的使用。

Query

询问

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

Variables

变数

{ 
    "userID":1
}

Output

输出量

别名 (Aliases)

Imagine a situation where we need to retrieve two different users. How do we identify each user? In GraphQL, you can't directly query for the same field with different arguments. Let's demonstrate this.

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

Query

询问

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

Variables

变数

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

Output

输出量

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

该错误非常具有描述性,甚至建议使用别名。 让我们更正实现。

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

Variables

变数

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

Output

输出量

Now we can correctly identify each user with their fields.

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

碎片 (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 out 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重复相同的字段。 我们可以找到可以查询到DRY的内容。 GraphQL包含称为片段的可重用单元,可让您构造字段集,然后将它们包含在需要的查询中。

Query

询问

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

fragment userFields on Person {
  name
  age
  gender
}

Variables

变数

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

Output

输出量

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

我们创建了一个名为userFields的片段,该片段只能应用于type Person并用它来检索用户。

指令 (Directives)

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

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

  • @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,则跳过此字段。

Let us say we want to retrieve users of gender 'F' but skip their age and include their id fields. We use variables to pass in the gender and use directives for the skipping and inclusion functionalities.

假设我们要检索性别为“ F”的用户,但跳过年龄并包括其id字段。 我们使用变量来传递性别,并使用指令进行跳过和包含功能。

Query

询问

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

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

Variables

变数

{
  "gender": "F",
  "age": true,
  "id": true
}

Output

输出量

变异 (Mutations)

So far we have been dealing with queries; operations to retrieve data. Mutations are the second main operation in GraphQL which deal with creating, deleting and updating of data. 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 their name and age and return the new user details.

到目前为止,我们一直在处理查询。 检索数据的操作。 变异是GraphQL中的第二个主要操作,它处理数据的创建,删除和更新。 让我们集中于一些如何进行突变的例子。 例如,我们要更新id==1的用户,并更改其年龄和姓名,并返回新的用户详细信息。

We will update our schema to include a mutation type and also update our root resolver with relevant resolver functions.

我们将更新架构以包括突变类型,并使用相关的解析器功能更新我们的根解析器。

// Add mutations
var schema = buildSchema(`
  ...
  type Mutation {
    updateUser(id: Int!, name: String!, age: String): Person
  }
`);
...
var updateUser = function({id, name, age}) {  // Update a user and return new user details
  users.map(user => {
    if(user.id === id) {
      user.name = name;
      user.age = age;
      return user;
    }
  });
  return users.filter(user=> user.id === id) [0];
}
...

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

Assuming this are the initial user details.

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

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

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

Query

询问

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

fragment userFields on Person {
  name
  age
  gender
}

Variables

变数

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

Output

输出量

结论 ( Conclusion )

So far we have covered from very basic concepts of GraphQL to some fairly complex examples. Most of these examples clearly reveal the differences between GraphQL and REST for users who have interacted with REST. To learn more about GraphQL, this series of articles provide a good background for its usage with React and other third party packages from Apollo.

到目前为止,我们已经涵盖了从GraphQL的基本概念到一些相当复杂的示例。 对于与REST交互的用户,大多数示例清楚地揭示了GraphQL和REST之间的区别。 要了解有关GraphQL的更多信息, 本系列文章提供了将其用于React和Apollo的其他第三方软件包的良好背景。

资源资源 (Resources)

翻译自: https://scotch.io/tutorials/a-practical-graphql-getting-started-guide-with-nodejs

node graphql

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码是一个C++函数的实现,函数名为Init(),下面逐行解释每一行的作用。 1. `ValueNode* head_node = new ValueNode[value_status_.total_size_];`:创建一个ValueNode类型的动态数组,数组长度为value_status_.total_size_,并将数组的首地址赋值给head_node指针变量。 2. `vec_memptr_.push_back(head_node);`:将head_node指针变量添加到vec_memptr_向量的末尾。 3. `ValueNode* tmp_node = head_node;`:将head_node指针变量的值赋给tmp_node指针变量。 4. `ValueNode* cur_node = tmp_node;`:将tmp_node指针变量的值赋给cur_node指针变量。 5. `for (uint32_t i = 1; i< value_status_.total_size_; i++) {`:for循环,循环变量i从1开始,每次增加1,循环条件是i小于value_status_.total_size_。 6. `cur_node->value_.node_ptr_ = (void*)cur_node;`:将cur_node指向的ValueNode结构体变量中的node_ptr_成员赋值为cur_node的地址。 7. `cur_node->next_node_ = tmp_node + i;`:将cur_node指向的ValueNode结构体变量中的next_node_成员赋值为tmp_node + i的地址。 8. `cur_node = cur_node->next_node_;`:将cur_node指针变量指向下一个ValueNode结构体变量。 9. `}`:结束for循环。 10. `value_status_.free_num_ = value_status_.total_size_;`:将value_status_结构体变量中的free_num_成员赋值为value_status_.total_size_的值。 11. `node_list_head_ = tmp_node;`:将node_list_head_指针变量的值赋为tmp_node的地址。 12. `node_list_tail_ = cur_node;`:将node_list_tail_指针变量的值赋为cur_node的地址。 13. `node_list_tail_->next_node_ = NULL;`:将node_list_tail_指向的ValueNode结构体变量中的next_node_成员赋值为NULL,表示链表的末节点。 14. `node_list_tail_->value_.node_ptr_ = (void*)node_list_tail_;`:将node_list_tail_指向的ValueNode结构体变量中的node_ptr_成员赋值为node_list_tail_的地址。 15. `rphead = NULL;`:将全局变量rphead赋值为NULL。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值