GraphQL from entry to practice

GraphQL from entry to practice

This article first introduces GraphQL, and then applies GraphQL through the combination of MongoDB + graphql + graph-pack, and elaborates on how to use GraphQL to add, delete, modify, and subscribe to push data. It also comes with examples of use, and I am impressed by learning while using it~

If you want to apply GraphQL to a production environment where the front and back ends are separated, please look forward to follow-up articles.

Example code for this article: Github

0. What is GraphQL

GraphQL is a data-oriented API query style.

The traditional API gets the agreed data format of the front and back ends. GraphQL provides a set of easy-to-understand and complete descriptions of the data in the API. The client can accurately obtain the data it needs without any redundancy. Easier to evolve over time and can also be used to build powerful developer tools.

1 Overview

Front-end development As the SPA framework is fully popularized, component-based development has also become the general trend. Each component manages its own state. While componentization brings convenience to the front-end, it also brings some troubles. For example, the component needs to be responsible for distributing the status of asynchronous requests to child components or notifying the parent component. In this process, the structural complexity caused by communication between components, data sources with unknown sources, and data responses from unknown sources will cause The data flow becomes chaotic, which also makes the code readability worse, and the maintainability is reduced, which brings great difficulties to future project iterations.

Imagine that you have finished the development, the product tells you to change a lot, from the interface to the component structure, and the back-end is also reluctant to cooperate and let you take data from several APIs and combine it yourself. Is it so sour?

In some scenarios with complex product chains, the backend needs to provide APIs corresponding to WebApp, WebPC, APP, small programs, fast apps, etc. At this time, the granularity of the API becomes extremely important, and coarse granularity will cause unnecessary traffic on the mobile end. Loss, fine-grained will cause function explosion (Function Explosion); in this scenario, Facebook engineers open sourced the GraphQL specification in 2015 , allowing the front end to describe the data form it wants, and the server returns the data structure described by the front end.

For simple use, please refer to the following picture:

For example, the front end want to return an ID for the 233user's name and gender, and look for the name and Email first ten employees of the user, and then find that person the father's phone, and the name of his father's dog (do not ask me why so Strange lookup?), then we can get all the information through a single query of GraphQL, without having to search back and forth from several asynchronous APIs:

query {
  user (id: "233") {
    name
    gender
    employee (first: 10) {
      name
      email
    }
    father {
      telephone
      dog {
          name
      }
    }
  }
}

The returned data format happens to be the data format provided by the front end, no more, no less, is it exciting?

2. Several important concepts

Here are a few more important concepts for understanding GraphQL, and other more complex usages such as instructions, union types, inline fragments, etc., refer to the GraphQL official website documentation~

2.1 Operation Type

GraphQL's operation type can be query, mutationor subscription, describing what kind of operation the client wants to perform

  1. query: Get data, such as search, R in CRUD
  2. Mutation changes: changes to data, such as addition, deletion, modification, CUD in CRUD
  3. Substription subscription: when the data changes, push news

These types of operations will be actually used later, for example, here is a query operation

query {
  user {id}
}

2.2 Object Type & Scalar Type

If a GraphQL service receives a query, then this querywill Root Querystart looking for, find it using analytic functions Resolver when the object type (Object Type) to get the content, if it was the type of object you continue to use the analytic function to get the content, if the return If it is a scalar type (Scalar Type), the acquisition ends until the last scalar type is found.

  1. Object type: defined by the user in the schema type
  2. Scalar type: GraphQL there are some built-in scalar type String, , Int, Float, Boolean, IDusers can also define your own scalar type

For example, declare in Schema

type User {
  name: String!
  age: Int
}

The Userobject type has two fields, name of a field is Stringnon-null scalar, age a field Intmay be empty scalar.

2.3 Schema

If you have used MongoOSE, then you should be familiar with the concept of Schema, which translates to "schema".

It defines the field type, data structure, and describes the rules of interface data request. When we make some wrong queries, the GraphQL engine will be responsible for telling us where there is a problem and detailed error information, which is very friendly to development and debugging.

Schema uses a simple strongly typed schema syntax called Schema Definition Language (SDL). We can use a real example to show how a real Schema file is written in SDL:

# src/schema.graphql

# Query entry
type Query {
    hello: String
    users: [User]!
    user(id: String): [User]!
}

# Mutation entrance
type Mutation {
    createUser(id: ID!, name: String!, email: String!, age: Int,gender: Gender): User!
    updateUser(id: ID!, name: String, email: String, age: Int, gender: Gender): User!
    deleteUser(id: ID!): User
}

# Subscription Entry
type Subscription {
    subsUser(id: ID!): User
}

type User implements UserInterface {
    id: ID!
    name: String!
    age: Int
    gender: Gender
    email: String!
}

# Enumeration type
enum Gender {
    MAN
    WOMAN
}

# Interface Type
interface UserInterface {
    id: ID!
    name: String!
    age: Int
    gender: Gender
}

This simple Schema file defines various object types or scalar types from the Query, Mutation, and Subscription entries. The types of these fields may also be other object types or scalar types, forming a tree structure, and the user is on the server side. When sending a request, select one or more branches along the tree to obtain multiple sets of information.

Note: Query is executed in parallel when querying fields, and when Mutation is changed, it is executed linearly, one after another , to prevent race problems caused by simultaneous changes. For example, we send two mutations in one request. , Then the previous one will always be executed before the next one.

2.4 Resolver

After the front-end request information arrives at the back-end, the parsing function Resolver needs to provide data, such as a Query:

query {
  hello
}

Then the analytic function of the same name should be like this

Query: {
  hello (parent, args, context, info) {
    return ...
  }
}

The analytic function accepts four parameters, namely

  1. parent: The current return value of the previous analytic function
  2. args: The parameters passed in the query
  3. context: Contextual information provided to all parsers
  4. info: A value that saves field specific information and schema details related to the current query

The return value of the parsing function can be a specific value, or it can be a Promise or an array of Promises.

Some commonly used solutions such as Apollo can help omit some simple parsing functions. For example, when a field does not provide a corresponding parsing function, the attribute with the same name as the field will be read and returned from the upper return object.

2.5 Request format

GraphQL is the most common to send requests through HTTP, so how to communicate with GraphQL through HTTP

For example, how to execute the following GraphQL query through Get/Post

query {
  me {
    name
  }
}

Get is the contents of the request in the URL, Post is content-type: application/jsonthe case, the contents of the JSON format on request in body

# Get way
http://myapi/graphql?query={me{name}}

# Post request body
{
  "query": "...",
  "operationName": "...",
  "variables": {"myVariable": "someValue", ...}
}

The returned format is generally JSON body

# Return correctly
{
  "data": {...}
}

# An error occurred during execution
{
  "errors": [...]
}

If an error occurs during execution, there will be detailed error information in the errors array, such as error information, error location, and call stack at the location where the error was thrown to facilitate positioning.

3. Actual combat

Here use MongoDB + graph-pack for a simple actual combat, and learn it together in actual combat. For the detailed code, please refer to Github ~

MongoDB is a widely used NoSQL. It is easy to find many ready-made solutions in the community, and it is easy to find a solution if an error is reported.

graph-pack is a zero-configuration GraphQL service environment that supports hot updates that integrates Webpack + Express + Prisma + Babel + Apollo-server + Websocket. Here it is used to demonstrate the use of GraphQL.

3.1 Environment deployment

First of all, we start MongoDB, this process will not be repeated, there are many tutorials on the Internet;

Take a look at the graph-pack environment

npm i -S graphpack

In package.jsonthescripts field plus:

"scripts": {
    "dev": "graphpack",
    "build": "graphpack build"
}

Create file structure:

.
├── src
│ ├── db//database operation related
│ │ ├── connect.js//database operation package
│ │ ├── index.js//DAO layer
│ │ └── setting.js//configuration
│ ├── resolvers//resolvers
│ │ └── index.js
│ └── schema.graphql//schema
└── package.json

Here schema.graphqlis an example of code sections 2.3, other implementations see Github , focused src/db , src/resolvers, src/schema.graphqlthree places

  1. src/db: Database operation layer, including DAO layer and Service layer (if you don’t know much about layering, you can read the last chapter)
  2. src/resolvers: Resolver parsing function layer, which provides resolver parsing functions for GraphQL's Query, Mutation, and Subscription requests
  3. src/schema.graphql: Schema layer

Then npm run dev, open the browser http://localhost:4000/can use GraphQL Playground start debugging, the left is requesting information bar, lower left column is the request parameters and request header set the bar on the right is the return parameter field, detailed usage can refer Prisma documents

3.2 Query

First we try hello world, we schema.graphqlQuery on a written entrancehello , it accepts the return value of type String

# src/schema.graphql

# Query entry
type Query {
    hello: String
}

in src/resolvers/index.js the corresponding supplement Resolver, Resolver this relatively simple, straightforward returns a String

//src/resolvers/index.js

export default {
    Query: {
        hello: () =>'Hello world!'
    }
}

We Query in the Playground

# Request
query {
  hello
}

# return value
{
  "data": {
    "hello": "Hello world!"
  }
}

Hello world is always so pleasant, let’s make a slightly more complicated query

Inlet query usersto find a list of all users, but returns a non-null array length may be 0, if the array elements, it must be of type User; another query inletuser accepts a string, to find the string user ID, and Returns a nullable field of type User

# src/schema.graphql

# Query entry
type Query {
    user(id: String): User
    users: [User]!
}

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

Increase the corresponding Resolver

//src/resolvers/index.js

import Db from'../db'

export default {
    Query: {
        user: (parent, {id }) => Db.user({ id }),
        users: (parent, args) => Db.users({})
    }
}

Here are two methods Db.user, Db.usersnamely, the function to find the corresponding data, returns Promise, if this is resolve Promise, then passed to resolve the data is returned as the result.

Then perform a query to find all the information we want

# Request
query {
  user(id: "2") {
    id
    name
    email
    age
  }
  users {
    id
    name
  }
}

# return value
{
  "data": {
    "user": {
      "id": "2",
      "name": "李四",
      "email": "mmmmm@qq.com",
      "age": 18
    },
    "users": [{
        "id": "1",
        "name": "Zhang San"
      },{
        "id": "2",
        "name": "李四"
      }]
  }
}

Note that the array returned only hope to get id, namethese two fields, so GraphQL and did not return the extra data, how kind, it is not very close

3.3 Mutation

If you know how to query data, you have to understand addition, deletion, and modification. After all, this is a must-have for CRUD engineers, but only the more complicated modifications are introduced here . You can check the other two methods on Github .

# src/schema.graphql

# Mutation entrance
type Mutation {
    updateUser(id: ID!, name: String, email: String, age: Int): User!
}

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

In the same way, Mutation also needs a Resolver to process the request

//src/resolvers/index.js

import Db from'../db'

export default {
    Mutation: {
        updateUser: (parent, {id, name, email, age }) => Db.user({ id })
            .then(existUser => {
                if (!existUser)
                    throw new Error('No one with this id')
                return existUser
            })
            .then(() => Db.updateUser({ id, name, email, age }))
    }
}

The Mutation entry updateUser first performs a user query after getting the parameters. If it is not found, an error will be thrown. This error will be returned to the user as an error message. Db.updateUserThis function returns a Promise, but it returns the information after the change.

# Request
mutation UpdataUser ($id: ID!, $name: String!, $email: String!, $age: Int) {
  updateUser(id: $id, name: $name, email: $email, age: $age) {
    id
    name
    age
  }
}

# Parameters
{"id": "2", "name": "王五", "email": "xxxx@qq.com", "age": 19}

# return value
{
  "data": {
    "updateUser": {
      "id": "2",
      "name": "Wang Wu",
      "age": 19
    }
  }
}

This completes the change to the data, and gets the changed data, and gives the desired fields.

### 3.4 Subscription

Another interesting aspect of GraphQL is that it can subscribe to data. After the front end initiates a subscription request, if the back end finds that the data changes, it can push real-time information to the front end. Let's take a look.

As usual, define the entry of Subscription in the Schema

# src/schema.graphql

# Subscription Entry
type Subscription {
    subsUser(id: ID!): User
}

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

Supplement its Resolver

//src/resolvers/index.js

import Db from'../db'

const {PubSub, withFilter} = require('apollo-server')
const pubsub = new PubSub()
const USER_UPDATE_CHANNEL ='USER_UPDATE'

export default {
    Mutation: {
        updateUser: (parent, {id, name, email, age }) => Db.user({ id })
            .then(existUser => {
                if (!existUser)
                    throw new Error('No one with this id')
                return existUser
            })
            .then(() => Db.updateUser({ id, name, email, age }))
            .then(user => {
                pubsub.publish(USER_UPDATE_CHANNEL, {subsUser: user })
                return user
            })
    },
    Subscription: {
        subsUser: {
            subscribe: withFilter(
                (parent, {id }) => pubsub.asyncIterator(USER_UPDATE_CHANNEL),
                (payload, variables) => payload.subsUser.id === variables.id
            ),
            resolve: (payload, variables) => {
                console.log('? Data received:', payload)
            }
        }
    }
}

Here pubsubis the apollo-server in charge of subscriptions and publishing class, which provides an asynchronous iterator in an interview with a subscription to the back end felt the need to publish subscribe when publishing payload to the front end. withFilterThe function is to filter out unwanted subscription messages. For detailed usage, refer to the subscription filter .

First we publish a subscription request

# Request
subscription subsUser($id: ID!) {
  subsUser(id: $id) {
    id
    name
    age
    email
  }
}

# Parameters
{"id": "2"}

We just update the data to be changed data once, and then we will get to and print out the pubsub.publishpayload release, thus completing the subscription data.

In graph-pack, data push is implemented based on websocket. You can open Chrome DevTools to take a look during communication.

4. Summary

The current structure of the front and back ends is roughly as shown below. The backend is connected to the database through the DAO layer to achieve data persistence, serving the Service layer that processes business logic. The Controller layer accepts API requests to call the Service layer for processing and returns; the front end uses the browser URL to route hits to obtain the target view state, and the page view It is composed of nested components, and each component maintains its own component-level state. Some slightly more complex applications also use centralized state management tools, such as Vuex, Redux, Mobx, etc. The front-end and back-end communicate only through API, which is also the basis for the separation of front-end and back-end development.

If GraphQL is used, the backend will no longer produce APIs, but maintain the Controller layer as a Resolver, and agree on a set of Schema with the frontend. This Schema will be used to generate interface documents, and the frontend will directly use the Schema or the generated interface documents to perform The request you expect.

After several years of filling holes by first-line developers, there are already some good tool chains that can be used for development and production. Many languages ​​also provide support for GraphQL, such as JavaScript/Nodejs, Java, PHP, Ruby, Python, Go, C# Wait.

Some of the more famous companies such as Twitter, IBM, Coursera, Airbnb, Facebook, Github, Ctrip, etc., internal or external APIs are converted from RESTful to GraphQL style, especially Github, its v4 version of external API only uses GraphQL. According to a boss working at Twitter, many first- and second-tier companies in Silicon Valley are trying to switch to GraphQL, but at the same time, he also said that GraphQL still needs time to develop, because it requires a lot of heavy front and back ends to use it in a production environment This undoubtedly requires high-level promotion and determination.

As You Yuxi said , why GraphQL was not widely used two or three years ago? There may be two reasons:

  1. If the field resolve of GraphQL is written in the naive way, each field runs a query directly to the database, which will generate a large number of redundant queries. Although the number of requests at the network level is optimized, database queries may become a performance bottleneck. There is a lot of room for optimization, but it is not so easy to do. FB itself does not have this problem, because their internal database is also abstracted away, and people who write GraphQL interfaces do not need to worry about query optimization.
  2. The benefit of GraphQL lies mainly in the development efficiency of the front-end, but the implementation requires the full cooperation of the server. If it is a small company or the entire company is a full stack, it may be possible to do it, but in many teams with a clear division of labor between the front and back ends, there will still be various collaboration resistances to promote GraphQL.

It can be roughly summarized as the reasons for performance bottlenecks and team division of labor. I hope that with the development of the community and the improvement of infrastructure, there will be gradually perfect solutions proposed, so that the majority of front-end and back-end developers can use this tool as soon as possible.

Most of the posts on the Internet are of different depths and even some are inconsistent. The following articles are summaries of the learning process. If you find errors, please leave a message to point out~

reference:

  1. GraphQL | A query language for your API
  2. JSON-RPC 2.0 specification-wiki .leozvc
  3. Why didn't GraphQL catch on?-You Yuxi's answer-Knowing
  4. What the hell is GraphQL | kazaff's blog
Reference: https://cloud.tencent.com/developer/article/1451722 GraphQL from entry to practice-Cloud + Community-Tencent Cloud