GraphQL Tutorial
Introduction
GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn't tied to any specific database or storage engine and is instead backed by your existing code and data.
Queries and Mutations
Fields
At its simplest, GraphQL is about asking for specific fields on objects.
{
lifebank {
name
}
}
{
"data": {
"lifebank": {
"name": "National Blood Bank"
}
}
}
This is essential to GraphQL, because you always get back what you expect, and the server knows exactly what fields the client is asking for.
Arguments
Every field and nested object can get its own set of arguments, making GraphQL a complete replacement for making multiple API fetches. You can even pass arguments into scalar fields, to implement data transformations once on the server, instead of on every client separately.
{
lifebank(email: "[email protected]") {
telephone
location
}
}
{
"data": {
"lifebank": {
"name": "National Blood Bank"
"location": "Costa Rica",
}
}
}
GraphQL comes with a default set of types, but a GraphQL server can also declare its own custom types, as long as they can be serialized into your transport format.
Aliases
Aliases allow you to rename the result of a field to whatever you want.
{
mainLifebank: lifebank(email: "[email protected]") {
name
}
secondaryLifebank: lifebank(email: "[email protected]") {
name
}
}
{
"data": {
"mainLifebank": {
"name": "National Blood Bank"
},
"secondaryLifebank": {
"name": "National Rescue Blood Bank"
}
}
}
Fragments
GraphQL includes reusable units called fragments. Fragments let you construct sets of fields, and then include them in queries where you need to.
{
leftComparison: lifebank(email: "[email protected]") {
...comparisonFields
}
rightComparison: lifebank(email: "[email protected]") {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
location
telephone
}
{
"data": {
"leftComparison": {
"name": "National Blood Bank",
"location": "Costa Rica",
"urgencyLevel": "high"
},
"rightComparison": {
"name": "National Rescue Blood Bank",
"location": "Costa Rica",
"urgencyLevel": "medium"
}
}
}
The concept of fragments is frequently used to split complicated application data requirements into smaller chunks.
Operation Name
The operation name is a meaningful and explicit name for your operation. It is only required in multi-operation documents, but its use is encouraged because it is very helpful for debugging and server-side logging.
query LifebankNameAndPhotos {
lifebank{
name
photos {
url
}
}
}
{
"data": {
"lifebank": {
"name": "National Blood Bank",
"photos": [
{
"url": "photo1.png"
},
{
"url": "photo2.png"
},
{
"url": "photo3.png"
}
]
}
}
}
When something goes wrong it is easier to identify a query in your codebase by name instead of trying to decipher the contents.
Variables
GraphQL has a first-class way to factor dynamic values out of the query, and pass them as a separate dictionary. These values are called variables.
When we start working with variables, we need to do three things:
- Replace the static value in the query with
$variableName. - Declare
$variableNameas one of the variables accepted by the query. - Pass variableName: value in the separate, transport-specific (usually JSON) variables dictionary.
{
"email": "[email protected]"
}
query LifebankNameAndPhotos($email: Lifebank) {
lifebank(email: $email) {
name
photos {
url
}
}
}
{
"data": {
"lifebank": {
"name": "National Blood Bank",
"photos": [
{
"url": "photo1.png"
},
{
"url": "photo2.png"
},
{
"url": "photo3.png"
}
]
}
}
}
Directives
A directive can be attached to a field or fragment inclusion, and can affect execution of the query in any way the server desires. The core GraphQL specification includes exactly two directives, which must be supported by any spec-compliant GraphQL server implementation:
@include(if: Boolean) Only include this field in the result if the argument is true.@skip(if: Boolean) Skip this field if the argument is true.
{
"email": "[email protected]",
"withPhotos": false
}
query Lifebank($email: Lifebank, $withPhotos: Boolean!) {
lifebank(name: $email) {
name
photos @include(if: $withPhotos) {
url
}
}
}
{
"data": {
"lifebank": {
"name": "National Blood Bank"
}
}
Mutations
Any query could be implemented to cause a data write. However, it is useful to establish a convention that any operation that causes writes must be explicitly sent through a mutation.
{
"email": "[email protected]",
"urgencyLevel": "high"
}
mutation ChangeUrgencyLevel($ep: Episode!, $review: ReviewInput!) {
updateUrgencyLevel(email: $email, urgencyLevel: $urgencyLevel) {
urgencyLevel
}
}
{
"data": {
"updateUrgencyLevel": {
"urgencyLevel": "high",
}
}
}
Similar to queries, if the mutation field returns an object type, you can request nested fields. This can be useful to get the new state of an object after an update.
Inline Fragments
If you are querying a field that returns an interface or a union type, you will need to use inline fragments to access data on the underlying concrete type.
{
"ep": "JEDI"
}
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
... on Human {
height
}
}
}
{
"data": {
"hero": {
"name": "R2-D2",
"primaryFunction": "Astromech"
}
}
}
Schema and Types
Type System
Every GraphQL service defines a set of types which completely describe the set of possible data you can query on that service. Then, when queries come in, they are validated and executed against that schema.
{
lifebank {
name
email
}
}
{
"data": {
"lifebank ": {
“name": "National Blood Bank",
"email": "[email protected]"
}
}
}
Type Language
GraphQL services can be written in any language. The "GraphQL schema language" - it's similar to the query language, and allows us to talk about GraphQL schemas in a language-agnostic way.
Object Types and Fields
The most basic components of a GraphQL schema are object types. In the GraphQL schema language, we might represent it like this:
type Character {
name: String!
appearsIn: [Episode!]!
}
-
Characteris a GraphQL Object Type, meaning it's a type with some fields. Most of the types in your schema will be object types. -
nameandappearsInare fields on the Character type. That means that name and appearsIn are the only fields that can appear in any part of a GraphQL query that operates on the ‘Character’ type. -
Stringis one of the built-in scalar types - these are types that resolve to a single scalar object, and can't have sub-selections in the query. -
[Episode!]!represents an array of Episode objects. Since it is also non-nullable, you can always expect an array (with zero or more items) when you query the ‘appearsIn’ field.
Arguments
Every field on a GraphQL object type can have zero or more arguments. All arguments in GraphQL are passed by name specifically, can be either required or optional. When an argument is optional, we can define a default value - if the unit argument is not passed, it will be set to METER by default.
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
The Query and Mutation Types
Every GraphQL service has a query type and may or may not have a mutation type. These types are the same as a regular object type, but they are special because they define the entry point of every GraphQL query.
schema {
query: Query
mutation: Mutation
}
query {
hero {
name
}
droid(id: "2000") {
name
}
}
{
"data": {
"hero": {
"name": "R2-D2"
},
"droid": {
"name": "C-3PO"
}
}
}
Mutations work in a similar way - you define fields on the Mutation type, and those are available as the root mutation fields you can call in your query.