"A query language for your API"
Can be used for an API that can in turn talk to:
Flexibility of GraphQL:
can describe complex tasks - the server-side API does not have to be adjusted for new use cases
Efficiency of GraphQL:
client can request exactly the data it needs in a single request
REST: endpoint (e.g. /todos
) and method (e.g. PUT
) are part of the API; a separate endpoint is needed for each type of query
GraphQL: single endpoint (e.g. /api
), all requests are POST requests
REST: complex cases require multiple HTTP requests
GraphQL: data are fetched via a single HTTP request
REST: Each endpoint always returns the same set of object fields
GraphQL: the client can request exactly the objects and fields it needs
query (GraphQL):
query {
random(min: 1, max: 100)
}
answer (JSON):
{
"random": 23
}
Query:
query {
user(login: "john") {
login
friends {
login
}
}
}
Response:
{
"user": {
"login": "john",
"friends": [
{ "login": "mike" },
{ "login": "stephanie" }
]
}
}
consuming an API:
defining an API:
Example shows use of a random number API which provides parameters to set the quantity and max value of the random numbers
schema definition:
type Query {
rand(max: Int!, quantity: Int!): [Int]!
}
resolver function (this depends on the library):
(root, args, context) =>
Array.from({ length: args.quantity }, () =>
Math.floor(Math.random() * args.max)
);
query random {
rand(max: 10, quantity: 3)
}
query:
query random($max: Int!, $quantity: Int!) {
rand(max: $max, quantity: $quantity)
}
query data (JSON):
{
"max": 10,
"quantity": 3
}
predefined API with posts and users:
Scenario:
A social media app in which we can view a list of our friends. Clicking on a friend takes us to their most recent posts
GET /users/$myuserid/friends
GET /users/$otheruserid/posts
{
users(id: "myuserid") {
friends {
userid
name
}
}
}
{
users(id: "otheruserid") {
posts {
date
title
body
}
}
}
Display new posts of all friends in a feed
In REST we would either have to send multiple requests to retrieve all posts of friends - or we would have to implement a new endpoint in the API:
GET /postsoffriends/$myuserid
In GraphQL we can implement this with just one request and without creating new endpoints:
{
users(id: "$myuserid") {
friends {
posts {
date
title
body
}
}
}
}
query {
user(login: "my-username") {
login
name
}
}
SELECT login, name
FROM user
WHERE login='my-username';
In GraphQL parameters don't have a predefined meaning.
In SQL the clause WHERE login='my-username'
always has the same meaning
In GraphQL the meaning of login: "my-username"
is up to the implementation on the server
SQL: relationships between tables (joins) are defined in the query
GraphQL: already knows about relationships when the query is issued → simpler queries
query {
user(login: "my-username") {
posts {
title
}
}
}
SELECT post.title
FROM user
LEFT JOIN post ON user.id = post.userId
WHERE user.login = 'my-username';
(extra code: LEFT JOIN post ON user.id = post.userId
)
OpenCRUD is a more specifc standard that is based on GraphQL. It maps directly to SQL and can be used in place of it.
from https://github.com/APIs-guru/graphql-apis:
template for simple todos on FakeQL:
{
"todos": [
{ "id": 1, "title": "Go shopping", "completed": true },
{ "id": 49, "title": "Do taxes", "completed": false },
{ "id": 50, "title": "Do dishes", "completed": false }
]
}
GraphiQL: Browser-based explorer for GraphQL APIs
query getTitles {
allFilms {
films {
title
}
}
}
query getPlanetsWithPopulations {
allPlanets {
planets {
name
population
}
}
}
query getStarshipsByFilm {
allFilms {
films {
title
starshipConnection {
starships {
name
}
}
}
}
}
Example on https://api.spacex.land/graphql/:
{
launchesUpcoming(limit: 4) {
launch_date_utc
mission_name
}
}
{
rocket(id: "falcon9") {
cost_per_launch
wikipedia
}
}
Advanced query - all Falcon 9 launches in 2019:
{
launchesPast(
find: { launch_year: "2019", rocket_name: "Falcon 9" }
) {
launch_date_utc
launch_site {
site_name
site_name_long
}
}
}
Note: The server-side implementation determines the set of supported parameters (e.g. find, id, limit ...)
Required parameters are (usually) marked with a !
. Returned attributes that will always be present (like id
) are marked in the same way.
query:
query getLaunchesByYear($year: String!) {
launchesPast(find: { launch_year: $year }) {
launch_date_utc
launch_site {
site_name
site_name_long
}
}
}
variables:
{
"year": "2019"
}
https://todo-mongo-graphql-server.herokuapp.com/
(supports only one query at a time)
Command that triggers the server's add
action and returns the id of the new TODO
mutation addTodo($title: String!) {
add(title: $title) {
id
}
}
{
"title": "shopping"
}
mutation toggleTodo($id: String!) {
toggle(id: $id) {
id
completed
}
}
mutation addOneAndClearCompleted($title: String!) {
add(title: $title) {
id
}
clearCompleted {
id
}
}
Task: write a query that will delete all previous entries and add two new ones
mutation reset {
toggleAll(checked: true) {
id
}
clearCompleted {
id
}
add(title: "get some rest") {
id
}
}
query {
user(login: "marko-knoebl") {
followers(first: 10) {
nodes {
login
followers(first: 10) {
nodes {
login
}
}
}
}
}
}
query {
user(login: "marko-knoebl") {
id
email
repositories(
first: 100
orderBy: { field: STARGAZERS, direction: DESC }
) {
nodes {
name
stargazers {
totalCount
}
}
}
}
}
With GraphQL the returned data types are always known to the client.
available types:
Queries are sent to the server via HTTP POST requests
The payload is a JSON object which has a query
string property (this is also true when sending mutations) and optionally a variables
property.
We can try this out from the browser console via fetch (we have to be on the same website):
const requestBody = {
query:
'mutation addTodo($title: String!) { add(title: $title) { id } }',
variables: '{"title": "test"}',
};
const requestBodyStr = JSON.stringify(requestBody);
fetch('https://todo-mongo-graphql-server.herokuapp.com', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: requestBodyStr,
}).then(console.log);
const queryTemplate = `
{
reddit {
subreddit(name: "javascript") {
newListings(limit: 2) {
title
}
}
}
}`;
fetch('https://www.graphqlhub.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({ query: queryTemplate }),
})
.then(r => r.json())
.then(data => console.log('data returned:', data));
query getPokemonByName($name: String = "Pikachu") {
pokemon(name: $name) {
number
image
}
}
Task: number of Pikachu and Raichu
This cannot be done the way we know:
query getTwo {
pokemon(name: "Pikachu") {
number
}
pokemon(name: "Raichu") {
number
}
}
Why does this not work? The result would look like this:
{
"data": {
"pokemon": {
"number": "025"
},
"pokemon": {
"number": "026"
}
}
}
Note the duplicate key: pokemon
!
In order to avoid this problem we use aliases:
query getTwo {
pokemon1: pokemon(name: "Pikachu") {
number
}
pokemon2: pokemon(name: "Raichu") {
number
}
}
response:
{
"data": {
"pokemon1": {
"number": "025"
},
"pokemon2": {
"number": "026"
}
}
}
Task: get the number, maxHP and image of Pikachu and Raichu
query getTwo {
pokemon1: pokemon(name: "Pikachu") {
...essentialData
}
pokemon2: pokemon(name: "Raichu") {
...essentialData
id
}
}
fragment essentialData on Pokemon {
number
maxHP
image
}