Delve into the usage syntax of GraphQL

The basic syntax for GraphQL was outlined in the previous section, but this article will expand on the previous introduction and try to cover all of it.

1. Terminal syntax

The syntax used in front end queries is divided into Query and Mutation, and the similarities between Query and Query are not specified.

1.1 the Query

Suppose we now have a data set with the following structure:

  1. Students and teachers have their own special fields;
  2. There is one way for students to get all the relevant teachers, and one way for teachers to get all the students;
  3. Students and teachers are many-to-many to each other;
classDiagram
    Student <|-- Teacher2Student
    Teacher <|-- Teacher2Student

    class Student {
        +Int id
        +String name
        +Int age
        +teachers(id_eq: number) get_all_teachers
    }
    class Teacher {
        +Int id
        +String name
        +Boolean gender
        +students(name_contains: string) get_all_students
    }
    class Teacher2Student {
        +Int id
        +Int student_id
        +Int teacher_id
        +student() get_student
        +teacher() get_teacher
    }

First of all, the simplest way to use can be checked:

Query {student {id name teacher {id name}}} # result: {data: {student: [{id: 1, name: "", teacher: [{id: 1] 1, name: "teacher li"}, {id: 2, name: "Mr Wu"}}, {id: 2, name: "bill," the teacher: [{id: 1, name: "teacher li"}]}, {id: 3, the name: "three", the teacher: [{id: 2, name: "Mr Wu"}]}}}]Copy the code

We can find from the above query that there are two students in total, and Mr Li taught both of them. This is the most basic use. Let’s slowly add query requirements to change the results.

1.1.1 parameters the Arguments

We can limit the contents of the return set by adding parameters

Query {student(name_contains: "3 ") {# <-- here limit name teacher(id_eq: {data: {student: [id: 1, name: teacher: [id: 1, name: teacher: [id: 2, name: teacher: [id: 2, name: teacher: [id: 2, name: teacher: [id: 2, name: teacher: [id: 2, name: teacher: [id: 2, name: teacher: [id: 2, name: teacher: [id: 2, name: teacher: [id: 2, name: teacher: [id: 2, name: teacher: "Mr Wu"}}, {id: 3, name: "three", the teacher: [{id: 2, name: "Mr Wu"}]}]}}Copy the code

At this time, because we filter the teachers whose ID is 2, So Miss Li is filtered out; Because the filter is set for all students whose names contain the word “three”, li Si is also filtered out.

Similarly, you can also use parameters to page, skip, and other operations, the same operation will not write the example.

1.1.2 alias Aliases

Because in the query, the different data entities in the Graphql statement themselves are similar to directly requesting the existence of this unit, it will report an error if you request two of the same sets at the same time; Because they should all be one to one. You can solve the problem with aliases:

Query {SAN: student(name_contains: "3 ") {id name} wang: student(name_contains:" 3 ") {id name}} # {SAN: [{id: 1, name: "* *"}] wang: [{id: 3, name: "three"}]}}Copy the code

When processing the result of the request, be aware that the aliased content is returned with the alias key, not the original name.

1.1.3 pieces Fragments could

Looking at the query above, we can see that it is inevitable to write a bunch of duplicate field names when using different aliases. In our example, a small number of fields is fine, but it is common for businesses to have dozens of fields, which would be too laborious to write, and Fragments can be created to handle them:

Fragment studentFields on Student {id name} query {SAN: Student (name_contains: 3) {... StudentFields} wang: student(name_contains: "contains ") {... StudentFields}} # studentFields: {data: {SAN: [{id: 1, name: "SAN"}] wang: [{id: 3, name: "SAN"}]}}Copy the code

1.1.4 Operation Name Operation Name

This is relatively less used, at least in my personal use, I basically prefer the principle of “save as much as you can”; But if you’re going to write a tutorial, I’ll do it. It is used to distinguish between operation data when there are multiple operations at the same time.

Query op1 {student(name_contains: "3 ") {id}} query op2 {student(name_contains:" 3 ") {id}} # op2 {student {id}}Copy the code

1.1.5 Operation parameters Variables

This parameter is different from Arguments mentioned above. Arguments are used for specific data node operations. Variables are operation-oriented ways to make Query reusable and easy to use in different places.

Suppose we have two different pages, both of which are querying the student table but with different filters, and if we write two queries like this it would be wasteful, ugly, and unreusable.

# 标 签 query {student(name_contains: "contains ") {id}} # 标 签 query {id}}Copy the code

In essence, they query the same content, but the parameters are slightly different. Here we can extract the parameters by passing the parameters in different cases in the actual use:

Query ($name: String) {student(name_contains: $name) {id}}Copy the code

Variables passed in while using, for example:

const query = gql` query($name: String) { student(name_contains: $name) { id } } `

const page1 = post(URL, query=query, variables={name: "Three"})
const page2 = post(URL, query=query, variables={name: "The king"})
Copy the code

The result is the same as writing two different Queries above, but the code is much more elegant and the Query is properly reused. If one day you need to modify the return result of a request, you don’t have to go around modifying the Query one by one.

Note that there are several hard and fast rules for defining parameters:

  1. Parameter names must start with $. There’s no negotiation not to start with a dollar sign it doesn’t recognize.
  2. The type of the parameter must be the same as where it will be used, otherwise an error will occur. Because Graphql is a statically typed language.
  3. Default values can be given in a similar way to TS, as shown below.
Query ($name: String = "3 ") {student(name_contains: $name) {id}}Copy the code

1.1.6 Directives

I think it is less intuitive that it translates to Directives. Its function is largely like a conditional modifier, like an if-else block in code. Allows you to specify the details of how to request outside.

query($name: String, $withTeacher: Boolean!) {
  student(name_contains: $name) {
    id
    teacher @include(if: $withTeacher) {
      id
    }
  }
}
Copy the code

The main thing it does is if you say withTeacher=true in the variables it will ask the teacher node, which is equivalent to:

query($name: String) {
  student(name_contains: $name) {
    id
    teacher {
      id
    }
  }
}
Copy the code

On the other hand, if withTeacher=false it will omit the teacher node, which is equivalent to:

query($name: String) {
  student(name_contains: $name) {
    id
  }
}
Copy the code

There are two major Directives: @include(if: Boolean) and @Skip (if: Boolean).

These two have opposite effects. In addition, this Directives requires support on the server. At the same time, the server can implement fully customized Directives if needed.

1.2 Mutation

1.2.1 Operation parameters Variables

This is exactly the same as the Query rule. See above for a small example:

# mutation create($name: String, $age: 18) {id}} Int) { createStudent(name: $name, age: $age) {id}} # Let createStudent ($input: createStudentInput!) { createStudent($input) { id } }Copy the code

1.2.2 Inline Fragments

The usage scenario here is primarily for Union types, similar to the relationship between interfaces and classes.

Suppose we have an interface called “Animal”, and we have two classes called “Dog” and “Bird”. And we give these two classes out by a GraphQL node:

{ animal { name kind ... on Dog { breed } ... On Bird {wings}}} {data: {animal: [{name: "Pepe", kind: "Dog", breed: "Husky"}, {name: "Pipi", kind: "Husky"} "Bird", wings: 2 } ] } }Copy the code

As you can see from the above results, it can look up different “classes” by different types, but it can return combined returns. It is similar to getting the data of the implementation class directly from an “interface”, very specific. But most of the time we might not be able to merge two different structures and return them in one array, we might be able to use the same node name (ANIMAL) to look up different things but filter them by their type first.

1.2.3 Meta Fields

With the example above, if we don’t have the kind field, how do we know which element is which? We can use meta fields to know which data entity we are currently working on. The main meta field is __typename.

We can check it like this:

{ animal { name __typename ... on Dog { breed } ... On Bird {wings}}} {data: {animal: [{name: "Pepe", __typename: "Animal__Dog", breed: "Husky"}, {name: {name: "Pepe", __typename: "Animal__Dog", breed: "Husky"}, "Pipi", __typename: "Animal__Bird", wings: 2 } ] } }Copy the code

__typename is built-in, you can look it up on any node and it will give you a type.

2. Type definition

2.1 basis

We know that GraphQL is a statically typed syntax system, so we have to define its type before we can actually use it. The type definition of GraphQL is called Schemas. It has its own independent syntax. There are various basic types, Scalar, which can be defined as the types of different objects. You can also define a new type from a base type yourself.

All of the different objects eventually form a tree structure, rooted in the schema:

schema {
  query: Query
  mutation: Mutation
}
Copy the code

Then define the layers of sub-objects inside. For example, our model above can be roughly written as:

type Query { student(name_contains: String): Student teacher(id_eq: ID): Teacher } type Student { id: ID! name: String! age: Int teachers: [Teacher!] ! } type Teacher { id: ID! name: String! gender: Boolean }Copy the code

In this way we define two different objects and their properties. Fields with “!” if they are required or non-empty After its type, such as ID: ID! That indicates that ID is a non-empty field. Non-empty fields will cause an error if NULL is passed to them during an operation. Arrays of other types can be made with the type enclosed in brackets, such as Teacher in Student above.

Define a field as an array:

myField: [String!]
Copy the code

By defining it this way, we mean that the field itself can be null, but it cannot have null members. Such as:

const myField: null // valid
const myField: [] // valid
const myField: ['a'.'b'] // valid
const myField: ['a'.null.'b'] // error
Copy the code

But if, by definition,

myField: [String]!
Copy the code

It cannot be null itself but its constituent members can contain null.

const myField: null // error
const myField: [] // valid
const myField: ['a'.'b'] // valid
const myField: ['a'.null.'b'] // valid
Copy the code

2.2 Built-in Types

GraphQL has only five default built-in types. Respectively is:

ID: Just like the ID field in a traditional database, it is used to distinguish different objects. It can be either an Int or an encoded unique value, such as relay, which uses the base64 transcoding of the string “class name :ID” as the ID. Note that this ID only exists in Graphql. It doesn’t necessarily correspond to what’s in the database. In the case of relay, the database may still have an integer instead of the string.

Int: an integer that can be positive or negative.

Float: double precision floating point number, which can be positive or negative.

String: utF-8 character String.

Boolean: true / false.

If that doesn’t suit your business scenario you can customize new types, or find a third party to make extensions.

The Graphql notation for defining a type is simple. For example, let’s add a Date type.

scalar Date
Copy the code

That’s it, but you need to implement it in your code, how to switch in and out of the runtime, and so on.

In addition, Graphql supports enumeration types, which can be defined as follows:

enum GenderTypes {
  MALE
  FEMALE
  OTHERS
}
Copy the code

2.3 Interface and Union Types

Interface and Union are very similar, so I’m going to put them together.

Interface, like other languages, is designed to give a generic parent type definition. It can be defined and used like this:

interface Animal {
  id: ID!
  name: String
}

type Dog implements Animal {
  id: ID!
  name: String
  breed: String
}

type Cat implements Animal {
  id: ID!
  name: String
  color: String
}
Copy the code

As you can see, each field defined by the interface is carried with it at implementation time, but it can also have its own field. When querying Animal, note that you can’t search for individual fields on Animal, because the system doesn’t know if you are currently querying for Dog or Cat. You need to specify that with an inline fragment.

# "Cannot query field \"color\" on type \"Animal\". Did you mean to use an inline fragment \"Cat\"?" Query {animal {id name}} # on Cat { color } } }Copy the code

So after Interface, let’s look at Union. Union you can think of directly as an Interface with no fields in common.

union Plant = Lily | Rose | Daisy
Copy the code

Inline fragments are used to specify the type of the query as is the case with the interface.

2.4 Input types

This was mentioned slightly above in the Mutation Variables example, which is to specify a type for all parameters of the input to an operation, making it easier to add content and increasing code reuse.

Suppose we have a definition of Mutation that goes like this:

type Mutaion {
  createSomething(foo: Int, bar: Float): Something
}
Copy the code

Using the Input types:

input CreateSomethingInput {
  foo: Int
  bar: Float
}

type Mutaion {
  createSomething(input: CreateSomethingInput): Something
}
Copy the code