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:
- 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
- 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:
- 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
- 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:
- Querying a User List
- 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