Why GraphQL?

Now there is a requirement to find the corresponding User information based on the article ID.

The data structure is as follows:

Users:
{
  "id": 1."fname": "Richie"."age": 27."likes": 8
},
{ 
  "id": 2."fname": "Betty"."age": 20."likes": 205
},
{ 
  "id" : 3."fname": "Joe"."age": 28."likes": 10
}
Copy the code
Posts:
{
  id: 1.userId: 2.body: "Hello how are you?"
},
{
  id: 1.userId: 3.body: "What's up?"
},
{
  id: 1.userId: 1.body: "Let's learn GraphQL"
}
Copy the code

1. Limitations of traditional methods

The common solution is to define two interfaces and obtain the user and POST information based on the user ID and POST ID respectively.

> https://localhost:4000/users/:id
> https://localhost:4000/posts/:id
Copy the code

The whole process is as follows:

GET https://localhost:4000/posts/1
Response:
{
  id: 1,
  userId: 2,
  body: "Hello how are you?"} we now GET the article id userId to 2, then call another interface for user information GET https://localhost:4000/users/2 Response: {"id": 2."fname": "Betty"."age": 20."likes": 205}Copy the code

The database was queried twice throughout the process.

That’s not all. What if the front-end page only uses fNAME information?

Some people say just fetch fname from the interface data. But did you realize that the interface returns all of the user’s fields: ID, age, likes, etc. These fields will consume network bandwidth and strain our servers as the number of requests increases.

Let me redefine an interface to return the user’s fname, for example:

https://localhost:4000/userByfname/:id
Copy the code

This does avoid the problems we mentioned above, but it does introduce some new ones: First, we need to create separate interfaces based on user’s field creation. Second, as the application changes, the user’s field information will certainly change, and we need to maintain these interfaces at the same time, which is a mental burden for developers.

So if we useGraphQLWhat will happen

2. GraphQL’s advantages

In GraphQL, just create a query statement:

{
  posts(id: 1) {
    body
    user {
      fname
    }
  }
}
Copy the code

If you only use the age field, you can modify the query statement:

{
  posts(id: 1) {
    body
    user {
      age
    }
  }
}
Copy the code

Just make sure that the User field is defined in advance in the GraphQL schema.

GraphQLThe biggest advantage is that there is no need to create a new interface, and no need to communicate with the back-end service many times, we can get the exact field information we want

GraphQL Basics

Let’s start with the most basic GraphQL operation type:

1, the Query

As the name implies, Query is used to Query data. It is passed to the server as a string in the body of the Post request. All GraphQL types are POST requests.

The following Query is used to fetch fname and age information from the database for all users:

query {
  users {
    fname
    age
  }
}
Copy the code

Ideally, the server should return data in this format:

data : {
  users [
    {
      "fname": "Joe"."age": 23}, {"fname": "Betty"."age": 29}]}Copy the code

If successful, a JSON object with key data is returned. If it fails, the key returned is error; We can handle errors based on this.

2, Mutation

Mutation is used to write data to the database. Think of it as post and PUT requests in REST:

mutation createUser{
  addUser(fname: "Richie", age: 22) {
    id
  }
}
Copy the code

A mutation named createUser was defined to add a user to the database based on the passed fname and age; We also define id as the return value:

data : {
  addUser : "a36e4h"
}
Copy the code

3, the Subscription

Subscription gives clients the ability to listen for changes in data in real time, using WebSockets.

subscription listenLikes {
  listenLikes {
    fname
    likes
  }
}
Copy the code

This defines two fields that listen on users: fname and LIKES. If any of these fields change, the client will be notified in the following form:

data: {
  listenLikes: {
    "fname": "Richie"."likes": 245}}Copy the code

This feature can be useful if you have a page that needs to display changes in the user’s likes field in real time.

These are the basic types of operations in GraphQL, and while they’re pretty simple, they’re enough. I’m going to use the above knowledge to design and implement a GraphQL example to get a feel for the power of GraphQL.

GraphQL

We first created a GraphQL service to respond to query, mutation and SUBSCRIPTIONS operations.

1. Initialization project

> mkdir graphql-server-demo
> cd graphql-server-demo
> npm init -y
Copy the code

Install dependencies:

> npm install apollo-server graphql nodemon --save
Copy the code

Apollo-server was introduced because it is an open source GraphQL server with powerful capabilities.

Add the following script to scripts in package.json:

"scripts": { "start": "nodemon -e js, json, graphql" }
Copy the code

2. Define data

In this example, we define the data as a JSON object.

In real projects, databases are often used to store data.

We define the data in index.js:

const users = [
  {
    id: 1.fname: 'Richie'.age: 27.likes: 0}, {id: 2.fname: 'Betty'.age: 20.likes: 205}, {id: 3.fname: 'Joe'.age: 28.likes: 10,},];const posts = [
  {
    id: 1.userId: 2.body: "Hello how are you?"
  },
  {
    id: 1.userId: 3.body: "What's up?"
  },
  {
    id: 1.userId: 1.body: "Let's learn GraphQL"},]Copy the code

Now that the preparation is in place, let’s implement the example.

3, the Schema

The first step is to design our Schema, which contains two interdependent objects: TypeDefs and Resolvers

3.1 TypeDefs

Earlier we learned about GraphQL’s operation types. To let the server know what types can be recognized and processed, TypeDefs defines these types:

const typeDefs = gql`
  type User {
    id: Int
    fname: String
    age: Int
    likes: Int
    posts: [Post]
  }
  type Post {
    id: Int
    user: User
    body: String
  }
  typeQuery { users(id: Int!) : User! posts(id: Int!) : Post! }type Mutation {
    incrementLike(fname: String!) : [User!]
  }
  type Subscription {
    listenLikes : [User]
  }
`;
Copy the code

First, we define a User, which contains five attributes: ID, fname, age, likes, and posts, and specify that these attributes can be of either String or Int type.

There are four basic types supported in GraphQL: String, Int, Float, Boolean. Add! Indicates that the field is non-empty.

We also defined Query, Mutation, and Subscription operations.

  • Define two Queries where Users receives an ID parameter and the return type is User. The other Query posts also receives an ID parameter with a return type of Post
type Query { users(id: Int!) : User! posts(id: Int!) : Post! }Copy the code
  • The Mutation name is incrementLike, which takes a parameter fname and returns an array of users.
type Mutation {
  incrementLike(fname: String!). : [User!] }Copy the code
  • Subscription is called listenLikes and returns an array of users.
type Subscription {
  listenLikes : [User]
}
Copy the code

Now, we just define the type, and the server doesn’t know how to respond to client requests, so we need to provide some processing logic for the type we define. Let’s call this Resolvers

3.2 Resolvers

Let’s continue writing resolvers:

const resolvers = {
  Query: {
    users(root, args) { return users.filter(user= > user.id === args.id)[0] },
    posts(root, args) { return posts.filter(post= > post.id === args.id)[0]}},User: {
    posts: (user) = > {
      return posts.filter(post= > post.userId === user.id)
    }
  },

  Post: {
    user: (post) = > {
      return users.filter(user= > user.id === post.userId)[0]}},Mutation: {
    incrementLike(parent, args) {
      users.map((user) = > {
        if(user.fname === args.fname) user.likes++
        return user
      })
      pubsub.publish('LIKES', {listenLikes: users});
      return users
    }
  },

  Subscription: {
    listenLikes: {
      subscribe: (a)= > pubsub.asyncIterator(['LIKES'])}}};Copy the code

As you can see, we created six resolvers functions:

Users Query returns the corresponding User object based on the id passed in. 2. Posts Query returns the corresponding Post object based on the id passed in. 3. Because the User TypeDefs contains a posts field, you need to specify a resolver to match posts posted by the User based on the User ID. Because the Post TypeDefs contains a user field, you need to specify a resolver to match the author of the article according to the userId. IncrementLike is used as a change operation to update the user’s likes field. The updated data is sent to the LIKES event via the message subscription publishing mode. ListenLikes, as a subscription object, listens for the LIKES event and responds to it.

What is subscription publishing? It is a real-time communication system based on WebSocket implemented by GraphQL. Websocket makes everything very simple.

Now that typedefs and Resolver are defined, our service is ready to go 🚀!

Open your browser, type http://localhost:4000/ and press Enter:

Apollo GraphQL provides an interactive Web application to test the results of the Query and mutations operations:

At this point, we have a simple GraphQL server that responds to Query, Mutation, and Subscription requests. 😁 doesn’t it feel cool?

Four,

GraphQL is both a query language for the API and a runtime for your data queries.

GraphQL provides an easy-to-understand set of complete descriptions of the data in your API, enabling a client to get exactly what it needs without any redundancy, making it easier for the API to evolve over time, and for building powerful developer tools.

To quote a great god who once said:

GraphQL will replace REST

Let’s wait and see.