GraphQL
What is it
GraphQL is a query language for API used to abstract the interaction between server and client. The language enables the use of declarative data fetching so that the client can specify exactly the data it's application needs and the server sends only the necessary data, avoiding the need for multiple HTTP requests to different endpoints or using bandwidth with unwanted data.
Some names to remember:
- Schema - It is the server definition of the types, queries, mutations, and subscriptions it can provide to the client.
- Query - Describes the data that the client wishes from the server. It's analogous to a
GET
request to a REST API. - Mutation - Describer the data manipulation within the server that the client wished to make. It's analogous to a
POST
request to a REST API. - Subscription - Used to listen for data update in the server using
WebSocket
. - Resolvers - Responsible for converting a GraphQL interaction (query, mutation, and subscription) in data. It's where the server implements the database access and business logic is implemented.
How we use
We currently use GraphQL for the API specification and data query between our backend and frontend projects. For the backend, we use the Apollo Server library to set up a graphql server and define the Schema and Resolvers. In the frontend, GraphQL is used with the Apollo Client, where each view defines the queries and mutations it needs.
We also use GraphQL in gatsby projects as it is its default mechanism of data querying.
How to learn
Usefull materials
- Official Documentation
- Overview of GraphQL and Diferences to REST
- GraphQL Fundamentals: HowToGraphQL or YouTube Playlist from Prisma developers.
- Backend Tutorial using Prisma and graphql-yoga (based in Apollo Server).
- Frontend Tutorial using React and Apollo Client.
- Creating a custom resolver using graphql-compose
Using Apollo to manage local state
- Local state management - Apollo docs
- Local State Management with Apollo - Video
- Apollo Link State in Less than 30 Minutes - Video
Validating knowledge
- Create a simple project consuming a ToDo API using the Apollo Client.
Simple Guides
Creating a custom resolver using graphql-compose
To simplify the schema creation process with the Apollo Server, we use the graphql-compose.
Here is a simple guide on defining a custom resolver with graphql-compose
:
Define a type for the result of the operation (Query/Mutation/Subscription)
import { schemaComposer } from 'graphql-compose';
...
const returnType = schemaComposer.createObjectTC({
// Define the name of the type, this will appear in the schema and can be used
// by other resolvers.
name: 'AwesomeQueryResult',
// Here we list the fields of the new type
fields: {
// You can use any type already defined in the schema by their name
message: 'String',
// It uses the same type notation as the GraphQL language, so for a required
// list of numbers, you should use
listOfNumbers: '[Int!]!',
// You can also use already created types
people: [myCustomPerson],
// Or define new types in place
proposals: schemaComposer.createObjectTC({ ... })
}
})
If needed, create any custom type for the input of the operation
// Inputs are defined in the same way as other types, it just uses a special
// function `createInputTC`.
//
// (In the GraphQL definition, the input types have some limitations when
// compared with other types, see the documentation for more details)
const inputType = schemaComposer.createInputTC({
name: 'AwesomeQueryInput',
fields: {
name: 'String!',
filter: 'JSON'
}
});
Define the arguments for the operation
// This is a simple object defined the same way as the `fields` property in the
// `createInputTC` and `createObjectTC`
const args = {
searchTerm: 'String!',
models: [inputType],
pagination: 'Pagination'
},
Define the resolver function
// This is the function that will be executed when the operation needs to be
// processed. The resolve function has an object with three property available:
// - source: The properties of the parent field, used for some nested queries
// - args: An object with the arguments passed into the field in the query
// - context: An object shared by all resolvers in a query. May contain
// authentication information and dataloader instances.
// - info: contains the information about the query state (we don't use it)
const resolveFn = ({ source, args, context, info }) => {
...
// The return of the resolve function must match the type defined earlier.
// Pay attention to required `!` types as null values can cause strange errors
// in the query result.
return { ... };
}
Create the schema resolver
// The key is the name of the query
const customResolver = schemaComposer.createResolver({
name: 'AwesomeQueryResolver',
// The return type of the query
type: returnType,
// The parameters definition for the query
args: args,
// The resolve function executed when the operation is being processed
resolve: resolveFn
})
Register the custom resolver as a Query, Mutation or Subscription
// The last step in the process is to register the custom operation in the
// RootQuery.
schemaComposer.Query.addFields({
awesomeQuery: customResolver
})
// If it is a Mutation, you can use
schemaCompose.Mutation.addFields({
awesomeMutation: customResolver
})