📜 ⬆️ ⬇️

Details about GraphQL: what, how and why

GraphQL now, without exaggeration, is the last squeak of the IT mode. And if you still do not know what kind of technology it is, how to use it, and why it might be useful to you, then the article, the translation of which we are publishing today, is written specifically for you. Here we will analyze the basics of GraphQL using the example of implementing a data scheme for an API company that deals with popcorn. In particular, let's talk about data types, queries and mutations.



What is GraphQL?


GraphQL is a query language used by client applications for working with data. GraphQL is associated with such a concept as “schema” - this is what allows you to create, read, update and delete data in your application (that is, we have four basic functions used when working with data storages, which are usually designated with the acronym CRUD - create, read, update, delete).

It was said above that GraphQL is used to work with data in “your application” and not “in your database”. The fact is that GraphQL is a system independent of data sources, that is, for the organization of its work, it doesn’t matter where the data is stored.
')
If you look, without knowing anything about GraphQL, at the name of this technology, it may seem that we have something very complicated and confusing. The name of the technology has the word "Graph". Does this mean that in order to master it, you will have to learn to work with graph databases? And the fact that the name has “QL” (which may mean “query language”, that is, “query language”), does it mean that those who want to use GraphQL will have to master a completely new programming language?

These fears are not fully justified. In order to reassure you - this is the cruel truth about this technology: it is just glittered GET or POST requests. While GraphQL, in general, introduces some new concepts regarding data organization and interaction, the internal mechanisms of this technology rely on good old HTTP requests.

Redefining REST technology


Flexibility is what distinguishes GraphQL technology from the well-known REST technology. When using REST, if everything is done correctly, endpoints are usually created taking into account the peculiarities of a certain resource or type of application data.

For example, when executing a GET request to the endpoint /api/v1/flavors it is expected that it will send a response that looks something like this:

 [ {  "id": 1,   "name": "The Lazy Person's Movie Theater",   "description": "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" }, {   "id": 2,   "name": "What's Wrong With You Caramel",   "description": "You're a crazy person that likes sweet popcorn. Congratulations." }, {   "id": 3,   "name": "Gnarly Chili Lime",   "description": "The kind of popcorn you make when you need a good smack in the face."} ] 

There is nothing catastrophically wrong with this answer, but we will think about the user interface, or rather, how we intend to consume this data.

If we want to display a simple list in the interface that contains only the names of the available types of popcorn (and nothing else), then this list may look like the one shown below.


List of popcorn

It is evident that here we are in a difficult situation. We may well decide not to use the description field, but are we going to sit back and pretend that we did not send this field to the client? And what else can we do? And when we, after a few months, are asked about why the application is so slow for users, we will only have to give the manager and no longer meet with the management of the company for which we have made this application.

In fact, the fact that the server sends unnecessary data in response to a client request is not entirely our fault. REST is a data acquisition mechanism that can be compared with a restaurant in which the waiter asks the visitor: “What do you want?”, And without paying special attention to his wishes, tells him: “I will bring you what we have” .

If we throw aside the jokes, then in real applications this can lead to problematic situations. For example, we can display a variety of additional information about each type of popcorn, like price information, manufacturer information, or nutritional information (“Vegan popcorn!”). At the same time, inflexible REST endpoints make it very difficult to obtain specific data on specific types of popcorn, which leads to an unreasonably high load on the systems and to the fact that the resulting solutions are far from those developers could be proud of.

How GraphQL Technology Improves What REST Technology Was Used For


With a superficial analysis of the situation described above, it may seem that we have only a minor problem. "What is wrong with the fact that we are sending unnecessary data to the client?". In order to understand the extent to which “unnecessary data” can become a big problem, let’s recall that GraphQL technology was developed by Facebook. This company has to serve millions of requests per second.

What does it mean? And the fact that with such volumes every trifle matters.

GraphQL, if we continue the analogy with the restaurant, instead of “carrying” the “what is” to the visitor, brings exactly what the visitor orders.

We can get a response from GraphQL that focuses on the context in which the data is used. At the same time, we don’t need to add “one-time” access points to the system, perform many queries or write multi-level conventional constructions.

How does graphQL work?


As we have said, GraphQL relies on simple GET or POST requests to transfer data to the client and receive it from it. If we consider this idea in more detail, it turns out that there are two types of queries in GraphQL. The first type includes requests for reading data, which in GraphQL terminology are simply called queries and refer to the letter R (reading, reading) of the acronym CRUD. Requests of the second type are data change requests, which in GraphQL are called mutations. They refer to the books C, U, and D of the CRUD acronym, that is, they use them to create (create), update (update) and delete (delete) entries.

All these requests and mutations are sent to the URL of the GraphQL-server, which, for example, may look like https://myapp.com/graphql , in the form of a GET or POST request. We'll talk more about this below.

GraphQL Queries


GraphQL queries are entities that represent a request to the server to get some data. For example, we have a certain user interface that we want to fill with data. For this data we turn to the server by executing the request. When using traditional REST API, our request takes the form of a GET request. When working with GraphQL, a new query building syntax is used:

 { flavors {   name } } 

Is that JSON? Or a JavaScript object? Neither one nor the other. As we already said, in the name of the GraphQL technology, the last two letters, QL, mean “query language”, that is, the language of queries. It is, literally, a new language for writing requests for data. All this sounds like a description of something rather complicated, but in reality there is nothing difficult here. Let's sort the above query:

 { //    ,   . } 

All queries begin with a “root query”, and what needs to be obtained during the execution of a query is called a field. In order to rid yourself of confusion, it is best to call these entities “request fields in the schema”. If such a name seems incomprehensible to you - wait a bit - below we'll talk more about the scheme. Here we, in the root query, request the flavors field.

 { flavors {   //  ,        flavor. } } 

When requesting a certain field, we must also indicate the nested fields that need to be obtained for each object that comes in response to the request (even if it is expected that only one object will be received in response to the request).

 { flavors {   name } } 

What happens in the end? After we send such a request to the GraphQL server, we will get a well-designed neat answer like the following:

 { "data": {   "flavors": [     { "name": "The Lazy Person's Movie Theater" },     { "name": "What's Wrong With You Caramel" },     { "name": "Gnarly Chili Lime" }   ] } } 

Please note that there is nothing superfluous. In order to make it clearer - here is another request that is executed to get data on another page of the application:

 { flavors {   id   name   description } } 

In response to this request, we will receive the following:

 { "data": {   "flavors": [     { "id": 1, "name": "The Lazy Person's Movie Theater", description: "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" },     { "id": 2, "name": "What's Wrong With You Caramel", description: "You're a crazy person that likes sweet popcorn. Congratulations." },     { "id": 3, "name": "Gnarly Chili Lime", description: "A friend told me this would taste good. It didn't. It burned my kernels. I haven't had the heart to tell him." }   ] } } 

As you can see, GraphQL is a very powerful technology. We refer to the same endpoint, and the answers to the requests correspond exactly to what is needed to fill the page from which these requests are executed.

If we need to get only one flavor , then we can take advantage of the fact that GraphQL can work with arguments:

 { flavors(id: "1") {   id   name   description } } 

Here we have rigidly specified in the code a specific identifier ( id ) of the object, information about which we need, but in such cases we can use dynamic identifiers:

 query getFlavor($id: ID) { flavors(id: $id) {   id   name   description } } 

Here, in the first line, we give the request a name (the name is chosen arbitrarily, getFlavor can replace getFlavor with something like pizza , and the request remains operable) and declare the variables that the request expects. In this case, it is assumed that the request will be given an identifier ( id ) of a scalar type ID (we will talk about types below).

Regardless of whether a static or dynamic id used when executing a query, this is how the response to such a query will look like:

 { "data": {   "flavors": [     { "id": 1, "name": "The Lazy Person's Movie Theater", description: "That elusive flavor that you begrudgingly carted yourself to the theater for, now in the comfort of your own home, you slob!" }   ] } } 

As you can see, everything is very comfortable. You are probably starting to think about using GraphQL in your own project. And, although what we already talked about looks great, the beauty of GraphQL truly manifests itself where it works with nested fields. Suppose that in our scheme there is another field called nutrition that contains information about the nutritional value of different types of popcorn:

 { flavors {   id   name   nutrition {     calories     fat     sodium   } } } 

It may seem that in our data warehouse, each flavor object will contain an embedded nutrition object. But it is not so. Using GraphQL, you can combine calls to independent, but related data sources in a single query, which allows you to get answers that make it easy to work with nested data without the need to denormalize the database:

 { "data": {   "flavors": [     {       "id": 1,       "name": "The Lazy Person's Movie Theater",       "nutrition": {         "calories": 500,         "fat": 12,         "sodium": 1000       }     },     ...   ] } } 

This can significantly increase the productivity of the programmer and the speed of the system.

So far we have been talking about read requests. What about data update requests? Does using them give us the same amenities?

GraphQL Mutations


While GraphQL queries load data, mutations are responsible for making changes to the data. Mutations can be used as a basic RPC mechanism (Remote Procedure Call, remote procedure call) for solving various tasks like sending data from a third-party API user.

When describing mutations, a syntax is used that resembles the one we used when forming queries:

 mutation updateFlavor($id: ID!, $name: String, $description: String) { updateFlavor(id: $id, name: $name, description: $description) {   id   name   description } } 

Here we declare the updateFlavor mutation, specifying some variables - id , name and description . Acting on the same scheme that is used in the description of requests, we “decorate” the variable fields (root mutation) using the keyword mutation , followed by the name describing the mutation, and the set of variables that are needed to form the corresponding data change request.

These variables include what we are trying to change, or what we want to cause a mutation. Note also that after the mutation has been completed, we can request the return of some fields.

In this case, we need to get, after changing the record, the id , name and description fields. This can come in handy when developing something like optimistic interfaces, eliminating the need to fulfill a request to get changed data after changing it.

Development of the scheme and its connection to the GraphQL-server


So far we have talked about how GraphQL works on the client, about how queries are executed. Now let's talk about how to respond to these requests.

QLGraphQL server


In order to execute a GraphQL query, you need a GraphQL server to which you can send such a query. A GraphQL server is a regular HTTP server (if you write in JavaScript, it can be a server created using Express or Hapi), to which the GraphQL schema is attached.

 import express from 'express' import graphqlHTTP from 'express-graphql' import schema from './schema' const app = express() app.use('/graphql', graphqlHTTP({ schema: schema, graphiql: true })) app.listen(4000) 

By “joining” a scheme, we understand the mechanism that passes requests received from the client through the scheme and returns answers to it. It is like an air filter through which air enters the room.

The "filtering" process is associated with requests or mutations sent by the client to the server. Both queries and mutations are resolved using functions associated with fields defined in the root query or in the root mutation of the schema.

The above is an example of an HTTP server framework created using the Express JavaScript library. Using the graphqlHTTP function from the express-graphql from Facebook, we “attach” the scheme (assuming that it is described in a separate file) and start the server on port 4000. That is, clients, if we talk about local use of this server, will be able to send requests over address http://localhost:4000/graphql .

â–ŤData types and resolvers


In order to ensure the operation of the GraphQL-server, you need to prepare a diagram and attach it to it.

Recall that above we talked about declaring fields in the root query or in the root mutation.

 import gql from 'graphql-tag' import mongodb from '/path/to/mongodb' //  -  . ,  `mongodb`     MongoDB. const schema = { typeDefs: gql`   type Nutrition {     flavorId: ID     calories: Int     fat: Int     sodium: Int   }   type Flavor {     id: ID     name: String     description: String     nutrition: Nutrition   }   type Query {     flavors(id: ID): [Flavor]   }   type Mutation {     updateFlavor(id: ID!, name: String, description: String): Flavor   } `, resolvers: {   Query: {     flavors: (parent, args) => {       // ,  args  ,  { id: '1' }       return mongodb.collection('flavors').find(args).toArray()     },   },   Mutation: {     updateFlavor: (parent, args) => {       // ,  args    { id: '1', name: 'Movie Theater Clone', description: 'Bring the movie theater taste home!' }       //  .       mongodb.collection('flavors').update(args)       //  flavor  .       return mongodb.collection('flavors').findOne(args.id)     },   },   Flavor: {     nutrition: (parent) => {       return mongodb.collection('nutrition').findOne({         flavorId: parent.id,       })     }   }, }, } export default schema 

The definition of fields in a GraphQL scheme consists of two parts — type declarations ( typeDefs ) and resolvers ( resolver ). The typeDefs contains type declarations for the data used in the application. For example, earlier we talked about a request to get a list of flavor objects from the server. In order for our server to perform a similar request, you need to do the following three steps:

  1. Inform the schema about how the given flavor objects look (in the example above, this looks like a type declaration of type Flavor ).
  2. Declare a field in the root type Query field type Query (this is the flavors property of type Query ).
  3. Declare a resolvers.Query function-resolvers.Query object, written in accordance with the fields declared in the type Query root field.

Let's pay attention now to typeDefs . Here we give the scheme information about the shape (shape) of our data. In other words, we are telling GraphQL about various properties that may be contained in entities of the corresponding type.

 type Flavor { id: ID name: String description: String nutrition: Nutrition } 

The type Flavor declaration indicates that the flavor object may contain an id field of type ID , a name field of type String , a description field of type String and a nutrition field of type Nutrition .

In the case of nutrition we use the name of another type declared in typeDefs . Here, the type Nutrition design describes the nutritional form of popcorn.

Please note that we are here, as at the very beginning of this material, talking about the "application" and not about the "database". In the example above, it is assumed that we have a database, but the data in the application can come from any source. It may even be a third-party API or a static file.

Just as we did in the type Flavor declaration, here we specify the names of the fields that will be contained in the nutrition objects, using, as the data types of these fields (properties), what in GraphQL is called scalar data types. At the time of this writing, GraphQL supported 5 built-in scalar data types :


In addition to these scalar types, we can assign properties and types that we define ourselves. That is what we did by assigning the nutrition property described in the type Flavor design, the Nutrition type.

 type Query { flavors(id: ID): [Flavor] } 

In the type Query construction, which describes the root type of the Query (the “root query” that we talked about earlier), we declare the name of the field that can be queried. When declaring this field, we, along with the data type that we expect to return, specify the arguments that can be received in the request.

In this example, we expect the possible arrival of the id argument of a scalar type ID . In response to such a request, an array of objects is expected whose device resembles a Flavor device.

â–ŤConnecting query resolver


Now that the root type Query has a definition of a field , we need to describe what is called a resolver function.

— , GraphQL, , «». resolvers , Query , , flavors , . flavors , type Query .

 typeDefs: gql`…`, resolvers: { Query: {   flavors: (parent, args) => {     // ,  args    { id: '1' }     return mongodb.collection('flavors').find(args).toArray()   }, }, … }, 

- . parent — , , args , . context , . «» ( — , ).

, , . GraphQL « » . , , .

GraphQL , , . JSON-, JSON-, ( GraphQL ).

- flavors MongoDB, args ( ) .find() , , .

â–Ť


-, GraphQL, , , , nutrition . , , Nutrition , , , , flavor . , / .

GraphQL , type Flavor nutrition type Nutrition , . , , flavor .

 typeDefs: gql`   type Nutrition {     flavorId: ID     calories: Int     fat: Int     sodium: Int   }   type Flavor {     […]     nutrition: Nutrition   }   type Query {…}   type Mutation {…} `, resolvers: {   Query: {     flavors: (parent, args) => {…},   },   Mutation: {…},   Flavor: {     nutrition: (parent) => {       return mongodb.collection('nutrition').findOne({         flavorId: parent.id,       })     }   }, }, 

resolvers , , Query , Mutation Flavor . , typeDefs .

Flavors , , nutrition -. , Flavor . , : « , nutrition , type Flavor ».

MongoDB, , parent , -. , parent , , , flavors . , flavor , :

 { flavors {   id   name   nutrition {     calories   } } } 

flavor , flavors , nutrition , parent . , , , MongoDB, parent.id , id flavor , .

parent.id , nutrition flavorId , flavor .

â–Ť


, , . , . type Mutation , , updateFlavor , , .

 type Mutation { updateFlavor(id: ID!, name: String, description: String): Flavor } 

: « , updateFlavor id ID ( , ! , GraphQL , ), name String description String ». , , Flavor ( — , id , name , description , , , nutrition ).

 { typeDefs: gql`…`, resolvers: {   Mutation: {     updateFlavor: (parent, args) => {       // ,  args    { id: '1', name: 'Movie Theater Clone', description: 'Bring the movie theater taste home!' }       //  .       mongodb.collection('flavors').update(         { id: args.id },         {           $set: {             ...args,           },         },       )       //  flavor  .       return mongodb.collection('flavors').findOne(args.id)     },   }, }, } 

- updateFlavor , : , , — , flavor .

, , flavor . Why is this so?

, , . , flavor , .

args ? , . , , , 100% , . , , , , , .

GraphQL?


, , , , , GraphQL-API.

, GraphQL , . , . , . , , , GraphQL REST . , , , GraphQL.

â–Ť ,


, HTTP-, , , , — . GraphQL , , , , ( ).

, , ( — ), GraphQL .

â–Ť , ,


, , « ». , , , . . GraphQL .

â–Ť ,


REST API, : , . , -, iOS Android, API . , , , , « » .

, , , HTTP, API (, , ).

▍ GraphQL — ? REST API GraphQL?


Of course not. . , , GraphQL . GraphQL, . , , , . , , .

, GraphQL , , , . GraphQL , Apollo Relay, .

GraphQL — , , . graphql ( express-graphql , ) — . , GraphQL - . , -, , , , .

Results


, GraphQL , . GraphQL , , , . , , , , GraphQL.

, : GraphQL . GraphQL . , GraphQL, , , , , , , .

— , GraphQL — , , . GraphQL , . , GraphQL — , , , . . , , , , , , GraphQL.

Dear readers! GraphQL — , .

Source: https://habr.com/ru/post/445268/


All Articles