GraphQL is a query language that needs to be mastered for both front-end and back-end development

When writing a service interface, you often encounter the following situations:

  1. Case 1: A user has information such as name, profile picture, phone number, qualification certificate (multiple), interests and hobbies. Only basic information such as name, profile picture and phone number is expected to be displayed on the user list page, while complete information is expected to be displayed on the user details page. When writing an interface, a user DTO is usually defined, which contains all information of the user. The DTO object is generated in both the list interface and the detail interface, but the list interface returns qualifications and interests that are redundant and consume additional query and network bandwidth
  2. Case 2: The interface to query the order list needs to return the order number, total price and information of the user who placed the order. The information of the user who placed the order is not in the order service. If the interface is written to traverse the queried order list and query the user information in the user service, it is definitely not feasible. If the front end itself to query the user information of each order, which increases the front-end workload, the front end will not be willing to

With that in mind, let’s take a look at the benefits of using GraphQL:

  1. It is up to the caller to set which fields the interface should return. In case 1, listing page 1 only needs name and profile picture, listing page 2 only needs name, phone number and qualification certificate, and listing page 3 only needs name, phone number and interests, without adjusting the code of the back-end interface
  2. For the second case, the order list interface returns the ID or unique identifier of the user who placed the order, and GraphQL queries the user information in the user service, and makes a cache after GraphQL queries. There is no need to modify the code of the back-end interface and front-end call, and the real front-end and back-end are good for everyone

The type definition

Basic types of

type instructions
String String type
Int A signed 32-bit integer
Float Signed double – precision floating-point type
Boolean Boolean
ID ID type, used for unique identification

Custom type

You can define a User type as follows

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

An array of

Add [] outside the type, such as [Int] [String] [User]

Exclamation mark

Add an exclamation mark to the right of the type to indicate that it cannot be empty, as in Int! String! User!

Create the GraphQL service

We use NodeJS to create the service, create a new directory, initialize and download the libraries we need

mkdir graphql-test
cd graphql-test
npm init -y
npm install express graphql node-fetch dataloader apollo-server -S
Copy the code

Start with a simple example of GraphQL

const fetch = require('node-fetch')
const DataLoader = require('dataloader')
const { ApolloServer, gql } = require('apollo-server')

// Define the backend interface URL
const BASE_URL = 'http://localhost:8088/api'

// Define the GraphQL request and type
const typeDefs = gql` type Query { findUser(id: ID!) : User } type User { id: ID name: String age: Int } `;

// Define the GraphQL processor
const resolvers = {
	Query: {
		findUser: resolveFindUser
	}
}

function resolveFindUser(_, { id }, context) {
	return {
		id: id,
		name: 'TodayNie ' + id,
		age: 18
	};
}

// Start the service
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) = > {
	console.log('GraphQL service started, access address:${url}`);
});
Copy the code

typeDefs

This is defining query methods and custom types

type Query { findUser(id: ID!) : User }Copy the code

FindUser (); findUser (); findUser (); findUser ()

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

Above is defining a custom type

resolvers

This is the business logic corresponding to the query method

const resolvers = {
	Query: {
		findUser: resolveFindUser
	}
}
Copy the code

FindUser: resolveFindUser: resolveFindUser: resolveFindUser: resolveFindUser: resolveFindUser: resolveFindUser: resolveFindUser: resolveFindUser

function resolveFindUser(_, { id }, context) {
	return {
		id: id,
		name: 'TodayNie ' + id,
		age: 18
	};
}
Copy the code

The resolveFindUser method takes root as its first entry, query as its second, and context as its third. As we will see below, this example simplifies the logic and returns a fixed object

Now let’s start this simple example

node index.js
Copy the code

After the startup, visit http://localhost:4000/ and see the following interface:

The left is the query interface, the right is the query results interface, in the left input query content

query {
  findUser(id: 1) {
    name
    age
  }
}
Copy the code

To query information about the user whose ID is 1, return only name and age

An example of requesting a back-end interface

Suppose the backend returns the following JSON format:

{
    "code":0."msg":""."data": {"id":"1"."name":"todaynie"."age":18}}Copy the code

Example Modify the resolveFindUser method

function resolveFindUser(_, { id }, context) {
	return fetch(`${BASE_URL}/index/test? id=${id}`).then(res= > res.json()).then(data= > data.data);
}
Copy the code

When requesting a back-end interface, you may need to print the result returned by the back-end interface. You can modify the resolveFindUser method to print the result returned by the back-end interface for debugging

return fetch(`${BASE_URL}/index/test? id=${id}`).then(res= > res.text()).then(data= > console.log(data));
Copy the code

Paging query

typeDefs

const typeDefs = gql` type Query { findUser(id: ID!) : User findUsers(page: Int, pageSize: Int): UserPage } type User { id: ID name: String age: Int } type UserPage { list: [User] pager: Pager } type Pager { page: Int pageSize: Int pageCount: Int recordCount: Int } `;
Copy the code

resolvers

function resolveFindUsers(_, { page, pageSize }, context) {
	return {
		list: [{id: 1.name: 'todaynie1'.age: 18 },
			{ id: 2.name: 'todaynie2'.age: 19 },
			{ id: 3.name: 'todaynie3'.age: 20},].pager: {
			page: 1.pageSize: 10.pageCount: 100.recordCount: 1000}}; }Copy the code

The query

query {
  findUsers(page: 1, pageSize: 10) {
    list {
      name
    }
    pager {
      page
      pageCount
    }
  }
}
Copy the code

Nested query

For example, if the user list interface is used to query the qualification certificate according to the returned user ID, or the user details page is used to query the qualification certificate after the basic user information interface is called, this query requires the result of the last query, and the root parameter is used

Modify the typeDef to add certs to User: [File]

type User {
  id: ID
  name: String
  age: Int
  certs: [File]
}

type File {
  name: String
  url: String
}
Copy the code

Adjust the resolvers

const resolvers = {
	Query: {
		findUser: resolveFindUser,
		findUsers: resolveFindUsers,
	},
  // User.certs is added to specify that when a User's certs field needs to be queried, it should be handled by resolveUserCerts
	User: {
		certs: resolveUserCerts,
	}
}

function resolveUserCerts(root, {}, context) {
  // You can print root, which is the User object
	console.log(root);
	return[{name: Certificate of 'XXX'.url: 'http://xxx' },
		{ name: Certificate of 'XXX'.url: 'http://xxx' },
		{ name: Certificate of 'XXX'.url: 'http://xxx'},]; }Copy the code

The query

query {
  findUser(id: 2) {
    name
    age
    certs {
      name
    }
  }
}
Copy the code

Nested queries are easy to implement

header

GraphQL as the middle layer, the front-end invokes a GraphQL query, and GraphQL invokes one or more back-end interfaces to get data returned to the front-end. In this process, the back-end interface may require token in the header to allow access, which requires the front-end to pass token to GraphQL. The GraphQL is passed to the back end, and the context parameter comes in handy

Modify index.js to add headers to context

const server = new ApolloServer({ typeDefs, resolvers });
server.context = async ({ req }) => {
    return {
        headers: req.headers,
    };
}
server.listen().then(({ url }) = > {
	console.log('GraphQL service started, access address:${url}`);
});
Copy the code

Modify the resolvers

function resolveFindUser(_, { id }, context) {
  // Print the header here, passing it in when the fetch method calls the back end, as long as it prints the correct result
	console.log(context.headers);
	return fetch(`${BASE_URL}/index/test? id=${id}`).then(res= > res.json()).then(data= > data.data);
}
Copy the code

Enter header for query

Query and view the console to obtain the token

The cache

The cache can be realized by using DataLoader, which can be found by searching online. It is used to slow down the fetch result and store it in the cache for the next query

conclusion

GraphQL allows each interface we develop on the back end to do a single job, like the example in this article that requires the back end to provide two interfaces:

  1. Querying a User List
  2. Query the certificate list based on the user ID

The user list interface does not need to search for users on the current page and then iterate to query the qualification certificate list of these users. If the qualification certificate is stored in a service specifically responsible for file management, the interface traversal query method requires remote invocation