Writing in the front

In the last article RPC vs REST vs GraphQL, we compared the advantages and disadvantages of the three in a macro way, and we also found that generally simple projects do not need GraphQL, but we still need to have a certain understanding and mastery of the new technology, so that we will not be surprised when the new technology is popular.

This article mainly introduces some core concepts that I think I need to understand during my contact with GraphQL, which is suitable for people:

  • Readers who have heard of GraphQL want to take a closer look
  • Readers who want to learn GraphQL systematically
  • Readers who are researching GraphQL technology

These concepts are not limited to the server side or the client side. If you are familiar with these concepts, you will be able to get started with the documentation of any library or framework that uses GraphQL as a technical background.

If you are already using GraphQL in a real project, this article may not be suitable for you, because it does not contain some practical summary and experience. I will write a separate article on practical summary later.

What is a GraphQL

There are dozens of articles on what GraphQL is, some long and some short, but at its core, it’s a query language and, by extension, an API query language.

Some of you might say, what? API can still check? Isn’t an API for calling? Yes, that’s the power of GraphQL, to quote the official documentation:

ask exactly what you want.

When we use THE REST interface, the data format and data type returned by the interface are predefined by the back end. If the returned data format is not expected by the caller, we can solve the problem in the following two ways as the front end:

  • Communicate with the back end, change the interface (change the data source)
  • Do some adaptation work yourself (work with data sources)

Generally, if it is a personal project, changing the back-end interface can be done at will, but if it is a company project, changing the back-end interface is often a sensitive matter, especially for the three terminals (Web, Android, ios) sharing the same set of back-end interfaces. In most cases, the problem is solved in the second way.

So if the return value of the interface can be changed from static to dynamic by some means, that is, the caller can declare what the interface returns, which further decoupled the relationship between the front and the back.

In GraphQL, we do this by pre-defining a Schema and declaring some types. We need to know:

  • The abstraction of the data model is described by Type
  • The logic for retrieving data for interfaces is described by Schema

It’s a little abstract, but let’s do it one by one.

Type

The abstraction of the data model is described in terms of Types, each of which consists of several fields, each referring to a Type.

GraphQL has two types, namely Scalar Type and Object Type.

Scalar Type

The built-in scalars in GraphQL include String, Int, Float, Boolean, and Enum, all of which should make sense to anyone familiar with programming languages.

Note that a new Scalar can be declared in GraphQL with Scalar, for example:

  • Prisma (a library that uses GraphQL to abstract database operations), andDateTimeandIDThese two scalars represent the date format and the primary key respectively
  • When implementing the file upload interface using GraphQL, you need to declare oneUploadScalar to represent the file to be uploaded

Anyway, just remember that scalars are the smallest particles in the GraphQL type system, and we’ll talk about them again when GraphQL parses the query results.

Object Type

Scalar quantities alone are not enough to abstract some complex data models, so we need to use object types, for example (ignore the syntax, just take it literally) :

type Article {
  id: ID
  text: String
  isPublished: Boolean
}
Copy the code

The above code declares an Article type with three fields: ID of type ID, text of type String, and isPublished of type Boolean.

For Field declarations of object types, we usually use a scalar, but we can also use another object type, for example, if we declare a new User type, as follows:

type User {
  id: ID
  name: String
}
Copy the code

At this point we can slightly change the declaration code for the Article type as follows:

type Article {
  id: ID
  text: String
  isPublished: Boolean
  author: User
}
Copy the code

The new author Field of Article is of type User, representing the author of the Article.

In summary, we use the object model to build the shapes of a data model in GraphQL, and we can also declare the internal relationships between the various models (one-to-many, one-to-one, or many-to-many).

Type Modifier

One more important concept about types is the Type modifier. There are currently two types of Type modifiers, List and Required, whose syntax is [Type] and Type! , and the two can be combined together, as in [Type]! Or [Type!] Or [Type!] ! Please look carefully here! , they mean:

  • The list itself is mandatory, but its internal elements can be empty
  • The list itself can be empty, but its internal elements are mandatory
  • Both the list itself and the internal elements are required

Let’s take the above example a step further and change it if we declare a new Comment type as follows:

type Comment {
  id: ID!
  desc: String,
  author: User!
}
Copy the code

You’ll find an ID here! , which means the Field is mandatory, and update the Article object as follows:

type Article {
  id: ID!
  text: String
  isPublished: Boolean
  author: User!
  comments: [Comment!]
}
Copy the code

The changes we made here are as follows:

  • Change the ID field to mandatory
  • The author field is changed to mandatory
  • New comments field, which is a List element of type Comment

The final Article type is a simpler type declaration for the Article data model in GraphQL.

Schema

Now let’s start with Schema. We briefly described what it does, which is to describe the logic for retrieving data from an interface, but it’s still a bit abstract. We can actually think of it as a URI for each individual resource in a REST architecture, but in GraphQL, We use Query to describe how resources are fetched. Therefore, we can think of a Schema as a table of multiple Queries.

Query is used in GraphQL to abstract the Query logic of data. There are three Query types in the current standard: Query, mutation, and subscription.

Note: For convenience, Query refers specifically to the Query in GraphQL (contains three types), and Query refers to the Query type in GraphQL (only refers to the Query type).

Query

The three basic Query types mentioned above exist as Root Queries. For traditional CRUD projects, the first two types are sufficient. The third type is proposed for the increasingly popular real-time applications.

Let’s take them literally, as follows:

  • Query (query) : When retrieving data, the query type should be selected
  • Mutation: The mutation type should be used when attempting to modify data
  • Subscription: You can push messages when you want data to change, using the Subscription type

Again, an example.

Firstly, we write a series of CRUD interfaces from the perspective of REST and GraphQL respectively, using Article as the data model, as follows:

Rest interface

GET /api/v1/articles/
GET /api/v1/article/:id/
POST /api/v1/article/
DELETE /api/v1/article/:id/
PATCH /api/v1/article/:id/

Copy the code

GraphQL Query

query { articles(): [Article!] ! article(id: Int): Article! } mutation { createArticle(): Article! updateArticle(id: Int): Article! deleteArticle(id: Int): Article! }Copy the code

In contrast to REST interfaces we are familiar with, GraphQL divides Query functions according to the type of the root Query, and also explicitly declares the data type returned by each Query. The syntax for type here is the same as in the previous chapter. It is important to note that any Query we declare must be a subset of Root Query because of the internal workings of GraphQL.

In this example, we only declare the Query and Mutation types. If our application has a real-time requirement for comment lists, in REST we might do so either directly via a long-join or by providing some interface to retrieve long-join urls with validation, such as:

POST /api/v1/messages/
Copy the code

Then the long connection pushes the new data to us, and in GraphQL, we declare it in a more declarative way, as follows

subscription {
  updatedArticle() { mutation node { comments: [Comment!] ! }}}Copy the code

We don’t have to worry about the syntax here, because the purpose of this Article is not to get you to learn the syntax of GraphQL in 30 minutes, but to understand some of its core concepts. For example, here we declare a subscription Query that pushes new data objects when new articles are created or updated. Of course, in practice, the internal implementation is still built on a long connection, but we can declare it in a more declarative way.

Resolver

If we only declare several Queries in the Schema, we are only half way there, because we do not provide the logic for the data returned by the relevant queries. In order for GraphQL to work properly, we need to understand one more core concept, Resolver.

In GraphQL, we have a convention where Query and its corresponding Resolver have the same name so that GraphQL can match them, for example, with articles(): [Article!] ! The name of the Query, Resolver, must be articles.

Before introducing Resolver, it’s time to take a look at the inner workings of GraphQL as a whole. Assuming now that we’re going to Query using our articles, we might write the following Query (again, ignoring the syntax for the moment) :

Query {
  articles {
  	 id
  	 author {
  	 	name
  	 }
  	 comments {
      id
      desc
      author
    }
  }
}
Copy the code

GraphQL parses the query as follows (abbreviated version) :

  • Let’s do the first level of parsing, the currentQuerytheRoot QueryType isqueryAnd I need its name to bearticles
  • We’ll try it laterarticlestheResolverGet parsed data, level 1 parsed
  • The value returned from the first level of parsing is then parsed in the second levelarticlesIt also contains three childrenQuery, respectively,id,authorandcomments
    • Id is a scalar type in Author type, and parsing is complete
    • Author is the object type User in the author typeUsertheResolverGet data, current field parsed
    • The third level of parsing is then performed on the value returned by the second level of parsingauthorIt also contains aQuery.nameSince it is a scalar type, parsing ends
    • The above comments…

As we can see, the general process of GraphQL parsing is to encounter a Query, try to use its Resolver value, and then parse the returned value. The process is recursive until the Type of the Field being resolved is Scalar Type. The whole process of parsing can be thought of as a long Resolver Chain.

This is just a brief overview of the GraphQL parsing process. The internal workings of GraphQL are much more complex than this. Of course, these are black boxes for users, so we only need to understand the process.

The declaration of the Resolver itself varies from language to language because it represents the concrete logic of fetching data. Its function signature (js as an example) looks like this:

function(parent, args, ctx, info) {
	...
}
Copy the code

The meanings of the parameters are as follows:

  • Parent: the return value of the last Resolver
  • Args: Pass a function in a Query (as in the example above)article(id: Int)In theid)
  • CTX: Intermediate variable passed continuously in the Resolver parsing chain (similar to context in middleware architecture)
  • Info: AST object of the current Query

It’s worth noting that the internal implementation of the Resolver is completely black box for GraphQL. This means that how the Resolver returns data, what kind of data it returns, and from where it returns data completely depends on the Resolver itself. Based on this, in practice, many people tend to use GraphQL as an intermediate layer, and data acquisition is encapsulated by Resolver. The implementation of internal data retrieval can be based on RPC, REST, WS, SQL, and many other different methods. Also, with this in mind, when migrating systems that don’t use GraphQL (such as REST), it’s a good idea to do incremental migration.

conclusion

That’s about it. Thank you for reading so far. Although the topic is 30 minutes to get familiar with the core concepts of GraphQL, I may have run out of time, but I believe you are familiar with the core concepts of GraphQL. But it’s a lot more than that, and it’s still in rapid development.

Finally, I try to provide some directions and suggestions for further learning and understanding GraphQL based on my experience of learning GraphQL in this period, just for reference:

To learn more about GraphQL itself

I recommend going to the official website and reading the official documentation, and if you’re interested, the Spec for GraphQL is also great. Although this article introduces the core concepts, other concepts are not involved, such as Union, Interface, Fragment, etc. These concepts are all based on the core concepts, which should be easy to understand after understanding the core concepts.

Biased server

On the server side, in addition to understanding GraphQL’s specific ecology in a particular language, you also need to know something about caching, uploading files, and other specific directions. If you want to do system migration, you also need to do some research on specific frameworks, such as graphene-Django.

Prisma is a framework built on GraphQL and compatible with some of the GraphQL ecosystem frameworks. Prisma can be used as an ORM in various programming languages. It can also be used as an intermediate layer to interact with the database.

Biased client

If you prefer the client side, you need to learn more about GraphQL-Client. What I have learned recently is Apollo, an open source grapqL-Client framework, and there are adaptation versions with various mainstream front-end technology stacks, such as Angular and React, which feel good to use.

You also need to understand some additional query concepts, such as Connection, Edge, and so on, which are involved in paging queries.

About this much, if there are mistakes, but also hope to correct.