This article first introduces GraphQL, and then through MongoDB + GraphQL + graph-pack combination of practical application GraphQL, detailed description of how to use GraphQL to add, delete, change and data subscription push, and with examples, while learning impressed ~

Look forward to a future article if you want to apply GraphQL to a production environment where the front and back ends are separated.

Example code for this article: Github

Interested students can add the end of the wechat group, discuss together ~

0. What is GraphQL

GraphQL is a data-oriented API query style.

While traditional apis take data formats that are conventionally formatted on the front and back ends, GraphQL provides a complete and easy-to-understand description of the data in the API. The client gets exactly what it needs without redundancy, makes it easier for the API to evolve over time, and can be used to build powerful developer tools.

1. An overview of the

With the comprehensive popularity of SPA framework, componentization development has also become the trend of The Times. Each component manages its own state, and componentization brings convenience to the front-end but also brings some troubles. Components need to be responsible for the asynchronous request, for example, the state of the distributed to child components or notice to the parent component, the process, the communication between components of structural complexity, source of data source, I don’t know where to subscribe data response makes up desultorily, also makes the code readability, and maintainability, This creates great difficulty for future iterations of the project.

Imagine that when you’re done, the product tells you that you need to change everything from the interface to the component structure, and the back end refuses to cooperate by letting you pull data from several apis and combine it yourself. 😅

In some complex product chain scenarios, the back-end needs to provide apis corresponding to WebApp, WebPC, APP, small program, fast application, etc. In this case, the GRANULARITY of API is particularly important. Coarse granularity will lead to unnecessary traffic loss on the mobile end. Fine particle size causes Function Explosion. In this case, Facebook engineers opened the GraphQL specification in 2015, allowing the front end to describe the data form it wants, and the server side to return the data structure described by the front end.

For simple use, see the following figure:

For example, the front-end wants to return the name and gender of a user with ID 233, look up the names and Email addresses of the user’s top ten employees, find the father’s phone number, and the name of the father’s dog (don’t ask me why I did this weird search 🤪). So we can get all the information from GraphQL in a single query, without having to go back and forth from several asynchronous apis:

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

The returned data format is just the front end provided data format, no more or less, is not the heart 😏

2. Several important concepts

Here are a few concepts that are important to understand GraphQL. For more complex uses like directives, union types, inline fragments, and so on, see the GraphQL official website documentation

2.1 Operation Type Operation Type

The operation type of GraphQL can be Query, mutation, or Subscription, which describes what the client wants to do

  1. Query: Gets data, such as lookup, from THE R in CRUD
  2. Mutation: Changes data, such as adding, deleting, or modifying the CUD in CRUD
  3. Substription: Pushes messages when data changes

These types of operations will be used in practice later in the article, such as here for a query operation

query {
  user { id }
}
Copy the code

2.2 Object Type and Scalar Type

If a GraphQL service receives a query, the query will start with the Root query and use its Resolver function to retrieve the content when it finds an Object Type. If the returned object Type is an object Type, the parser function will continue to retrieve the contents, and if the returned Scalar Type is a Scalar Type, the fetching will end until the last Scalar Type is found.

  1. Object type: defined by the user in the Schematype
  2. Scalar types: GraphQL has some scalar types built inString,Int,Float,Boolean,ID, users can also define their own scalar types

For example, declare in Schema

type User {
  name: String!
  age: Int
}
Copy the code

The User object type has two fields. The name field is a non-empty scalar of String, and the age field is an empty scalar of Int.

2.3 model Schema

If you’ve used MongoOSE, you’re familiar with the concept of Schema, which translates to “Schema.”

It defines the types of fields, the structure of the data, and describes the rules of the interface data request. When we make some wrong queries, the GraphQL engine will be responsible for telling us where the problem is, and the detailed error information. It 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 using SDL:

# src/schema.graphql

# Query entrance
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 entrance
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
}
Copy the code

This simple Schema file defines object types or scalar types, starting with Query, Mutation, and Subscription entries. The types of these fields may also be other object types or scalar types, forming a tree structure, and when the user sends a request to the server, Selecting one or more branches along the tree gives you access to multiple sets of information.

Note: When querying Query fields, they are executed in parallel, whereas when Mutation changes, they are executed linearly, one after the other, to prevent race problems caused by simultaneous changes. For example, if we send two mutations in a request, the previous one will always be executed before the last.

2.4 The Resolver function

When the front-end request information reaches the back-end, it needs to be provided by the Resolver function, such as a Query like this:

query {
  hello
}
Copy the code

So the analytic function of the same name would look something like this

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

The analytic function takes four arguments, which are

  1. parent: The return value of the previous parse function
  2. args: Parameter passed in the query
  3. context: Context information provided to all parsers
  4. info: a value that holds field-specific information and schema details related to the current query

The return value of a parse function can be a concrete value or a Promise or an array of promises.

Some common solutions, such as Apollo, can help omit simple parsing functions, such as reading and returning properties with the same name from the upper return object when a field does not provide a corresponding parsing function.

2.5 Request Format

GraphQL is most commonly used to send requests over HTTP, so how do you communicate GraphQL over HTTP

For example, how do I execute the following GraphQL query using Get/Post

query {
  me {
    name
  }
}
Copy the code

Get is to put the request content in the URL, and Post is to put the JSON format content in the body of the request in the case of content-type: application/json

http://myapi/graphql? Query ={me{name}} {"query": "..." , "operationName": "..." , "variables": { "myVariable": "someValue", ... }}Copy the code

The format returned is usually JSON body

{"data": {... }} # {"errors": [...] }Copy the code

If an error occurs during execution, the Errors array contains detailed error information, such as the error information, the location of the error, the call stack at the throw site, and so on, for easy locating.

3. The actual combat

Here use MongoDB + graph-pack for a simple combat, and learn together in combat, detailed code see Github ~

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

Graph-pack is a zero-configuration GraphQL server environment that integrates Webpack + Express + Prisma + Babel + Apollo-server + Websocket with hot update support. This is used here to demonstrate GraphQL usage.

3.1 Environment Deployment

First of all, we started MongoDB, and I won’t go into this, there’s a lot of tutorials online;

Set up the graph pack environment

npm i -S graphpack
Copy the code

In the package.json field, add:

"scripts": {
    "dev": "graphpack",
    "build": "graphpack build"
}
Copy the code

Create file structure:

. ├ ─ ─ the SRC │ ├ ─ ─ db / / database operations related │ │ ├ ─ ─ the connect. Js/packages/database operations │ │ ├ ─ ─ index. The js / / DAO layer │ │ └ ─ ─ setting. The js / / configuration │ ├ ─ ─ ├ ─ ch.pdf // ch.pdf │ ├ ─ ch.pdf // ch.pdf │ ├ ─ ch.pdf // ch.pdfCopy the code

Schema.graphql is a sample code from Section 2.3. For other implementations, see Github. The main focus is on SRC /db, SRC /resolvers, SRC /schema.graphql

  1. src/db: Database operations layer, including DAO layer and Service layer (see the last chapter if you are not familiar with layers)
  2. src/resolvers: Resolver parsing function layer, provides the Resolver parsing function for GraphQL’s Query, Mutation, and Subscription requests
  3. src/schema.graphql: Schema layer

Then NPM run dev, the browser opens http://localhost:4000/ and you can start debugging with the GraphQL Playground. On the left is the request information bar, on the bottom left is the request parameters bar and the request header setting bar, and on the right is the return parameters bar. See the Prisma documentation for details

3.2 the Query

First, let’s try Hello World. In schema.graphQL, we write hello as an entry for Query that accepts a String return value

# src/schema.graphql

# Query entrance
type Query {
    hello: String
}
Copy the code

SRC /resolvers/index.js add the corresponding Resolver, which is simple and returns a String directly

// src/resolvers/index.js

export default {
    Query: {
        hello: (a)= > 'Hello world! '}}Copy the code

We are querying in our Playground

{"data": {"hello": "Hello world!" }}Copy the code

Hello World is always a pleasure, so let’s do a slightly more complex query

The users query entry searches for a list of all users and returns a non-empty array of 0 length. If there are elements in the array, they must be of type User. The other query entry, user, takes a string, looks for a user with that string ID, and returns a nullable field of type USER

# src/schema.graphql

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

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

Add 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({})
    }
}
Copy the code

If the Promise is resolved, the data passed to resolve is returned as the result. If the Promise is resolved, the data passed to resolve is returned as the result.

Then we do a query to find all the information we want

# Query {user(id: "2") {id name email age} users {id name}} # Query {user(id: "2") {id name email age} users {id name}} # Query {user(id: "2") {id name email age} users {id name}} "Bill", "email" : "[email protected]", "age" : 18}, "users" : [{" id ":" 1 ", "name" : "* *"}, {" id ":" 2 ", "name" : "bill"}]}}Copy the code

Note that GraphQL returns only the id and name fields. Therefore, GraphQL does not return any extra data

3.3 Mutation

You need to know how to query the data, add, delete, and modify it. These are the basics of a CRUD engineer, but here are only the more complicated ones. For the other two, check out 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!
}
Copy the code

Similarly, Mutation also needs a Resolver to process requests

// 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('Anyone who doesn't have this ID')
                return existUser
            })
            .then((a)= > Db.updateUser({ id, name, email, age }))
    }
}
Copy the code

The Mutation entry updateUser performs a user query first, and if it does not find the parameter, it throws an error, which is returned to the user as an error message. The db.updateuser function also returns a Promise, but returns the changed information

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

# 参数
{"id": "2", "name": "王五", "email": "[email protected]", "age": 19}

# 返回值
{
  "data": {
    "updateUser": {
      "id": "2",
      "name": "王五",
      "age": 19
    }
  }
}
Copy the code

This completes the change to the data, takes the changed data, and gives you the desired field.

3.4 the Subscription

Another interesting aspect of GraphQL is that it can subscribe to data. After the current 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, an entry for Subscription is defined in the Schema

# src/schema.graphql

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

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

Add 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('Anyone who doesn't have this ID')
                return existUser
            })
            .then((a)= > 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('🚢 received data: ', payload)
            }
        }
    }
}
Copy the code

Pubsub is the subscribing and publishing class in apollo-server. It provides an asynchronous iterator to accept a subscription, and publishes payload to the front end when the back end feels the need to publish a subscription. The withFilter function is to filter out the subscription messages that are not needed. See the subscription filter for details.

First we publish a subscription request

# Request subscription subsUser($id: id! {subsUser(id: $id) {id name age email}} #Copy the code

We use the data update operation just now to make a data change, and then we will get the pubsub.publish payload and print it out, thus completing the data subscription.

In graph-Pack, data push is implemented based on WebSocket, so you can open Chrome DevTools to see it during communication.

4. To summarize

The current structure of the front and back ends is as follows. The backend connects with the database through the DAO layer to realize the data persistence, and serves the Service layer that handles the business logic. The Controller layer accepts THE API request and calls the Service layer for processing and returns. The front end obtains the state of the target view by routing the browser URL, while the page view is composed of nested components, each of which maintains its own component level state. Some more complex applications use centralized state management tools, such as Vuex, Redux, Mobx, and so on. The front and back end communicate only through an API, which is the basis of the separate front and back end development today.

If you use GraphQL, instead of producing an API, the back end maintains the Controller layer as a Resolver with a set of schemas that the front end uses to generate interface documents. The front-end makes its desired requests directly through the Schema or generated interface documents.

After a few years of work by front-line developers, there are some good toolchains available for development and production, and many languages offer GraphQL support, such as JavaScript/Nodejs, Java, PHP, Ruby, Python, Go, C#, etc.

Some of the more well-known companies, such as Twitter, IBM, Coursera, Airbnb, Facebook, Github, Ctrip, etc., have changed their internal or external apis from RESTful to GraphQL style, especially Github, Its VERSION V4 external API uses only GraphQL. According to a Twitter executive, many first – and second-tier companies in Silicon Valley are trying to move to GraphQL, but he also said that GraphQL will take time to evolve because it will require a lot of refactoring on the front and back ends, which will undoubtedly require a lot of commitment from the top.

As Youyuxi said, there are probably two reasons why GraphQL wasn’t widely used two or three years ago:

  1. GraphQL field Resolve: If you are naive, each field will run a query directly to the database, resulting in 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’s a lot of room for optimization, but it’s not that easy to do. FB itself doesn’t have this problem, because their internal database layer is also abstracted away, and the person who writes the GraphQL interface doesn’t need to worry about Query optimization.
  2. The benefit of GraphQL is mainly the development efficiency of the front end, but the landing needs the full cooperation of the server side. If you’re a small company or an entire company with a full stack, you might be able to do it, but there’s a lot of collaborative resistance to pushing GraphQL in teams with a clear front end division.

It can be summarized as the performance bottleneck and team division. I hope that with the development of the community and the improvement of the infrastructure, there will gradually be a perfect solution, so that the vast number of front-end developers can use this tool as soon as possible.


Most of the online posts are different in depth, and even some inconsistent, the following article is a summary of the learning process, if you find mistakes, welcome to leave a message to point out ~

Reference:

  1. GraphQL | a raw query language for your API
  2. Json-rpc 2.0 specification – wikis. Leozvc
  3. Why didn’t GraphQL catch on? – Yu Yuxi’s answer – Zhihu
  4. What the hell GraphQL | kazaff ‘s blog

PS: Welcome everyone to pay attention to my public number [front afternoon tea], refueling together ~

In addition, you can join the wechat group of “front end afternoon tea Communication Group”. Long press to identify the qr code below to add my friend, note to add group, I pull you into the group ~