The fields of the Query and Mutation types are the entry points into a graph. .
Fields
Each type is composed of one or more fields, which can be scalar values or complex objects that refer to other types, resulting in nesting. Each GraphQL query specifies which schema fields to return. This is referred to as the query’s selection set.
Consider the following schema excerpt:
type Query {
topRatedMovies: [Movie]
}
type Movie {
id: ID!
title: String
year: Int
plot: String
poster: String
imdbRating: Float
genres: [String]
similar(limit: Int = 3): [Movie]
rating: RATING
actors: [Actor]
avgStars: Float
}
Here is a valid query against that schema:
{
topRatedMovies {
title
year
avgStars
}
}
And here is an example result of that query:
{
"data": {
"topRatedMovies": [
{
"title": "Godfather, The",
"year": 1972,
"avgStars": 4.4875
},
{
"title": "Shawshank Redemption, The",
"year": 1994,
"avgStars": 4.487138263665597
},
{
"title": "On the Waterfront",
"year": 1954,
"avgStars": 4.448275862068966
}
]
}
}
Arguments
Schema fields can accept arguments, which can be required or optional. Each argument can optionally have a default value if one isn’t provided.
The following Query.moviesByTitle
field accepts two arguments. The limit argument specifies a default value:
type Query {
moviesByTitle(title: String!, limit: Int = 3): [Movie]
}
Here is an example query that provides the required title
argument:
{
moviesByTitle(title: "Matrix Reloaded") {
title
similar(limit: 1) {
title
year
}
}
}
And here is an example result:
{
"data": {
"moviesByTitle": [
{
"title": "Matrix Reloaded, The",
"similar": [
{
"title": "Matrix, The",
"year": 1999
}
]
}
]
}
}
Variables
GraphQL variables enable you to provide values for arguments without hardcoding their values directly into a query string. This helps prevent injection attacks that use string interpolation to build queries from user-supplied content. To use variables, first declare $varName
as a valid variable for the query, then replace the value in the query with $varName
. Finally, pass the key-value pair varName
: value in a dictionary alongside the query.
Here’s an example query with two variables:
query MoviesByTitle($movieTitle: String!, $similarLimit: Int) {
moviesByTitle(title: $movieTitle) {
title
similar(limit: $similarLimit) {
title
year
}
}
}
And here’s a variables object that’s provided alongside that query:
{
"movieTitle": "Matrix Reloaded",
"similarLimit": 1
}
Result:
{
"data": {
"moviesByTitle": [
{
"title": "Matrix Reloaded, The",
"similar": [
{
"title": "Matrix, The",
"year": 1999
}
]
}
]
}
}
Mutations
While queries enable you to read back-end data, GraphQL mutations enable you to create, update, and delete that data. To enable mutations, a schema defines a Mutation
type. The fields of this type are entry points that correspond to different write operations that clients can execute.
Consider a UserReview
type:
type UserReview {
user: User
rating: Int
movie: Movie
}
To create a new UserReview
, a schema might define this Mutation
field:
type Mutation {
createReview(userId: ID!, movieId: ID!, rating: Int): UserReview
}
Note that the mutation returns a UserReview
object. This means that you can access any of the fields available on Movie
and User
(in a nested fashion):
Mutation:
mutation {
createReview(userId: "20", movieId: "16", rating: 5) {
movie {
title
similar(limit: 2) {
title
}
}
user {
name
}
rating
}
}
Result:
{
"data": {
"createReview": {
"movie": {
"title": "Casino",
"similar": [
{
"title": "Four Brothers"
},
{
"title": "Night and the City"
}
]
},
"user": {
"name": "Nicole Ramsey"
},
"rating": 5
}
}
}
Input Types
In the previous mutation example, three individual primitive arguments were passed to the createReview
field. Instead, this field could accept a single input type that includes all of those individual primitive values as fields. Input types are especially useful for mutations where the goal is to pass an update as a single object.
Input type:
input ReviewInput {
rating: Int!
movieId: ID!
userId: ID!
}
Then modify the createReview
field to accept one argument of type ReviewInput
:
type Mutation {
createReview(review: ReviewInput!): UserReview
}
The mutation becomes:
mutation CreateReviewForMovie($review: ReviewInput) {
createReview(review: $review) {
movie {
title
}
user {
name
}
rating
}
}
Variables:
{
"review": {
"movieId": "16",
"userId": "20",
"rating": 5
}
}
Results:
{
"data": {
"createReview": {
"movie": {
"title": "Casino"
},
"user": {
"name": "Nicole Ramsey"
},
"rating": 5
}
}
}
Fragments
A fragment is a reusable set of fields that can be defined and referenced by name in multiple GraphQL queries. This enables you to factor out common logic and reduce repetition in operations. To apply a fragment inside a query, use a fragment spread operator (...) inside the selection set:
{
moviesByTitle(title: "Matrix Reloaded") {
...movieSummaryFields
}
}
fragment movieSummaryFields on Movie {
title
year
imdbRating
}
Result:
{
"data": {
"moviesByTitle": [
{
"title": "Matrix Reloaded, The",
"year": 2003,
"imdbRating": 7.2
}
]
}
}
Inline Fragments
Inline fragments are useful for queries where a field’s return type is determined at runtime. These fragments use the syntax ...on <TYPE>
. They are primarily used when a field’s return type is an abstract type (i.e., a union or interface).
Consider the following:
type Query {
personByName(name: String!): [PersonResult]
}
union PersonResult = User | Actor
PersonResult
can be either a User
or Actor
. In this case, an inline fragment can be used to fetch the appropriate fields for each type in the union:
{
personByName(name: "Tom Cruise", limit: 3) {
... on Actor {
name
movies {
title
}
}
... on User {
name
}
}
}
Result:
{
"data": {
"personByName": [
{
"name": "Tom Cruise",
"movies": [
{
"title": "Risky Business"
},
{
"title": "Cocktail"
},
{
"title": "Top Gun"
}
]
}
]
}
}
Subscriptions
Often, clients want to receive updates from the server when certain data changes. In addition to queries and mutations, GraphQL supports a third operation type, subscription, which allows a client to subscribe to receive event updates. Subscriptions are real-time streaming chunks of data that allow bi-directional interaction over a single persistent connection (often WebSocket).
Let's say a client wants to receive updates when a user submits a movie rating
for a particular movie
. If the user is viewing a movie page, the UI would update when a new rating
is received.
Every time the underlying MovieReviewSubscription
is changed, the new value for rating
will be pushed over WebSocket (or some other type of persistent connection). The nature of operations performed by subscription
is slightly different than that of query
— the former has real-time fetching of data while the latter fetches only once.
Subscription operation:
subscription MovieReviewSubscription($movieId: ID!) {
movieReviewSubscription(movieId: $movieId) {
movie {
title
}
rating
}
}
Directives
GraphQL directives enable static and dynamic schema modification. Here are two built-in directive examples:
@skip
|
@include
|
query myMovieQuery($testValue: Boolean!) {
experimentalField @skip(if: $testValue)
}
In this example, experimentalField is queried only if the variable $testValue has the value false. |
query myMovieQuery($testValue: Boolean!) {
experimentalField @include(if: $testValue)
}
In this example, experimentalField is queried if the variable $testValue has the value true. |
Learn more about directives here.
Securing Your GraphQL APIs
Failing to secure APIs properly can have serious consequences, especially with GraphQL. Because clients can craft complex queries, servers must be ready to handle such queries. Certain queries might be abusive or malicious, or they might simply be very large. In all of these cases, the query can potentially bring down your GraphQL server. GraphQL recommends a few guidelines and strategies.
Timeout
Setting a timeout informs a server and any intermediaries of the maximum amount of time they can spend to execute any query. If a query exceeds this maximum, the server ceases execution.
Limiting Maximum Query Depth
GraphQL schemas are often cyclic graphs, meaning a malicious GraphQL query can exploit nested relationships. For example, let’s say there’s an Entertainment API that exposes an Actor
type. Each Actor
can belong to a Movie
, and each Movie
can have an Actor
in the form of an Artist
. Considering this, a deeply nested query such as the following is valid:
A circular reference like this can lead to significant load on API servers.
GraphQL servers can enforce depth-limiting caps to control the amount of nesting that can be present in any given query. Before the query is processed, its depth is calculated (the above query has six nesting levels). If the nesting depth exceeds the given limit, it is rejected outright.
Query Complexity Limits
Another troublesome query relates to complexity — for example, a client can simply request many fields on the root query object.
To deal with this kind of query, GraphQL has built-in support for complexity analysis. An arbitrary number can be assigned as points to any field. When an incoming query is received, these arbitrary numbers are summed. If the total exceeds the threshold, an appropriate error message is returned.
Field-Based Permissions
Given a schema with the objects Users
, Bookings
, and Movies
, the Bookings
and Users
objects are private, so authentication is needed to access them. Authentication is handled by resolvers. The Movies
object is public and can be accessed without being authenticated. Now, what should happen with the query below if the client is not authenticated?
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}