Edit Page

GraphQL API Tutorial with RESTHeart

🔧 Configuration

Sets localhost:8080 with admin:secret
Values are saved in your browser

This tutorial guides you through implementing a GraphQL API using RESTHeart, following the structure and concepts from the GraphQL.org tutorial on Star Wars characters and episodes.

Dive into the world of GraphQL and RESTHeart, and may the force be with your data!

Create the Star Wars GraphQL API

Important
This tutorial requires RESTHeart v7.6.4+
Note
You won’t write a single line of code! The GraphQL API only requires defining a JSON configuration, known as a GraphQL App Definition, and storing it in the special collection /gql-apps.

1 Start RESTHeart and MongoDB

The following one-liner uses Docker Compose to start both RESTHeart and MongoDB. If you prefer not to use Docker, see the setup section to install RESTHeart and MongoDB.

$ curl https://raw.githubusercontent.com/SoftInstigate/restheart/master/docker-compose.yml --output docker-compose.yml && docker compose up

2 Create collections

Create the star-wars-characters collection. This is where the data will be inserted.

cURL

curl -i -X PUT [INSTANCE-URL]/star-wars-characters \
  -H "Authorization: Basic [BASIC-AUTH]"

HTTPie

http PUT [INSTANCE-URL]/star-wars-characters Authorization:"Basic [BASIC-AUTH]"

JavaScript

fetch('[INSTANCE-URL]/star-wars-characters', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]'
  }
})
.then(response => {
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

Create the gql-apps collection. This is where the GraphQL App definition will be inserted.

cURL

curl -i -X PUT [INSTANCE-URL]/gql-apps \
  -H "Authorization: Basic [BASIC-AUTH]"

HTTPie

http PUT [INSTANCE-URL]/gql-apps Authorization:"Basic [BASIC-AUTH]"

JavaScript

fetch('[INSTANCE-URL]/gql-apps', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]'
  }
})
.then(response => {
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

3 Insert data

Create documents in the star-wars-characters collection:

cURL

curl -i -X POST [INSTANCE-URL]/star-wars-characters?wm=upsert \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '[
    {"_id":1000,"name":"Luke Skywalker","friends":[1002,1003,2000,2001],"appearsIn":[4,5,6],"homePlanet":"Tatooine","heroOf":4},
    {"_id":1001,"name":"Darth Vader","friends":[1004],"appearsIn":[4,5,6],"homePlanet":"Tatooine","heroOf":5},
    {"_id":1002,"name":"Han Solo","friends":[1000,1003,2001],"appearsIn":[4,5,6],"homePlanet":"Corellia"},
    {"_id":1003,"name":"Leia Organa","friends":[1000,1002,2000,2001],"appearsIn":[4,5,6],"homePlanet":"Alderaan"},
    {"_id":1004,"name":"Wilhuff Tarkin","friends":[1001],"appearsIn":[4],"homePlanet":"Eriadu"},
    {"_id":2000,"name":"C-3PO","friends":[1000,1002,1003,2001],"appearsIn":[4,5,6],"primaryFunction":"Protocol"},
    {"_id":2001,"name":"R2-D2","friends":[1000,1002,1003],"appearsIn":[4,5,6],"primaryFunction":"Astromech","heroOf":6},
    {"_id":3000,"name":"TIE Advanced x1","length":9.2}
  ]'

HTTPie

http POST [INSTANCE-URL]/star-wars-characters wm==upsert \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json \
  <<< '[
    {"_id":1000,"name":"Luke Skywalker","friends":[1002,1003,2000,2001],"appearsIn":[4,5,6],"homePlanet":"Tatooine","heroOf":4},
    {"_id":1001,"name":"Darth Vader","friends":[1004],"appearsIn":[4,5,6],"homePlanet":"Tatooine","heroOf":5},
    {"_id":1002,"name":"Han Solo","friends":[1000,1003,2001],"appearsIn":[4,5,6],"homePlanet":"Corellia"},
    {"_id":1003,"name":"Leia Organa","friends":[1000,1002,2000,2001],"appearsIn":[4,5,6],"homePlanet":"Alderaan"},
    {"_id":1004,"name":"Wilhuff Tarkin","friends":[1001],"appearsIn":[4],"homePlanet":"Eriadu"},
    {"_id":2000,"name":"C-3PO","friends":[1000,1002,1003,2001],"appearsIn":[4,5,6],"primaryFunction":"Protocol"},
    {"_id":2001,"name":"R2-D2","friends":[1000,1002,1003],"appearsIn":[4,5,6],"primaryFunction":"Astromech","heroOf":6},
    {"_id":3000,"name":"TIE Advanced x1","length":9.2}
  ]'

JavaScript

fetch('[INSTANCE-URL]/star-wars-characters?wm=upsert', {
  method: 'POST',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify([
    {"_id":1000,"name":"Luke Skywalker","friends":[1002,1003,2000,2001],"appearsIn":[4,5,6],"homePlanet":"Tatooine","heroOf":4},
    {"_id":1001,"name":"Darth Vader","friends":[1004],"appearsIn":[4,5,6],"homePlanet":"Tatooine","heroOf":5},
    {"_id":1002,"name":"Han Solo","friends":[1000,1003,2001],"appearsIn":[4,5,6],"homePlanet":"Corellia"},
    {"_id":1003,"name":"Leia Organa","friends":[1000,1002,2000,2001],"appearsIn":[4,5,6],"homePlanet":"Alderaan"},
    {"_id":1004,"name":"Wilhuff Tarkin","friends":[1001],"appearsIn":[4],"homePlanet":"Eriadu"},
    {"_id":2000,"name":"C-3PO","friends":[1000,1002,1003,2001],"appearsIn":[4,5,6],"primaryFunction":"Protocol"},
    {"_id":2001,"name":"R2-D2","friends":[1000,1002,1003],"appearsIn":[4,5,6],"primaryFunction":"Astromech","heroOf":6},
    {"_id":3000,"name":"TIE Advanced x1","length":9.2}
  ])
})
.then(response => {
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

4 Get the GraphQl App Definition

Download the GraphQL app definition json file.

curl -s https://restheart.org/docs/mongodb-graphql/graphql-app-definition.json --output graphql-app-definition.json

5 Insert the GraphQL App Definition

To insert the GraphQL app definition, run the following:

cURL

curl -i -X POST [INSTANCE-URL]/gql-apps?wm=upsert \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d @graphql-app-definition.json

HTTPie

http POST [INSTANCE-URL]/gql-apps wm==upsert \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json \
  < graphql-app-definition.json

JavaScript

fetch('[INSTANCE-URL]/gql-apps?wm=upsert', {
  method: 'POST',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    // GraphQL App Definition JSON content
    // See the full definition below
  })
})
.then(response => {
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
Note
Request body omitted because is quite long. Find it in the The full GraphQL APP definition section.
That’s it

The GraphQL API is ready to use!

Execute queries

hero

cURL
curl -i -X POST [INSTANCE-URL]/graphql/star-wars \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/graphql" \
  -d '{
    hero(episode: JEDI) {
        name
        friends {
            name
        }
        ... on Droid {
            primaryFunction
        }
        ... on Human {
            homePlanet
        }
    }
  }'
HTTPie
echo '{
    hero(episode: JEDI) {
        name
        friends {
            name
        }
        ... on Droid {
            primaryFunction
        }
        ... on Human {
            homePlanet
        }
    }
  }' | http POST [INSTANCE-URL]/graphql/star-wars \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/graphql
JavaScript
fetch('[INSTANCE-URL]/graphql/star-wars', {
  method: 'POST',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/graphql'
  },
  body: `{
    hero(episode: JEDI) {
        name
        friends {
            name
        }
        ... on Droid {
            primaryFunction
        }
        ... on Human {
            homePlanet
        }
    }
  }`
})
.then(response => {
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
{
    "data": {
        "hero": {
            "name": "R2-D2",
            "friends": [
                {
                    "name": "Luke Skywalker"
                },
                {
                    "name": "Han Solo"
                },
                {
                    "name": "Leia Organa"
                }
            ],
            "primaryFunction": "Astromech"
        }
    }
}
cURL
curl -i -X POST [INSTANCE-URL]/graphql/star-wars \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/graphql" \
  -d '{
    search(text: "an") {
        ... on Character {
            name
        }
        ... on Starship {
            name
            length
        }
    }
  }'
HTTPie
echo '{
    search(text: "an") {
        ... on Character {
            name
        }
        ... on Starship {
            name
            length
        }
    }
  }' | http POST [INSTANCE-URL]/graphql/star-wars \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/graphql
JavaScript
fetch('[INSTANCE-URL]/graphql/star-wars', {
  method: 'POST',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/graphql'
  },
  body: `{
    search(text: "an") {
        ... on Character {
            name
        }
        ... on Starship {
            name
            length
        }
    }
  }`
})
.then(response => {
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
{
    "data": {
        "search": [
            {
                "name": "Han Solo"
            },
            {
                "name": "Leia Organa"
            },
            {
                "name": "TIE Advanced x1",
                "length": 9.2
            }
        ]
    }
}

Understanding the GraphQL App Definition

GraphQL types are connected to MongoDB data through mappings.

Note
If you don’t explicitly define a mapping for a field, RESTHeart will automatically map it to the MongoDB document field with the same name.

enum Episode

The GraphQL schema defines the enum Episode:

enum Episode { NEWHOPE EMPIRE JEDI }

The enum type requires a mapping unless the value in the database is identical to the string representation of the enum value. If you look at the data, the episode are stored with Int codes. Thus we need a mapping to link the Int codes to the enum values.

Tip
for more information on enum mappings see Enum mappings
{
    "mappings": {
        "Episode": { "NEWHOPE": 4, "EMPIRE": 5, "JEDI": 6 }
    }
}

union SearchResult

The GraphQL schema defines the union SearchResult:

union SearchResult = Human | Droid | Starship

The union requires a $typeResolver

Tip
for more information on union mappings see Union Mappings
{
    "mappings": {
        "SearchResult": {
            "$typeResolver": {
                "Human": "field-exists(homePlanet)",
                "Droid": "field-exists(primaryFunction)",
                "Starship": "field-exists(length)"
            }
        }
    }
}

interface Character

The GraphQL schema defines the interface Character:

interface Character {
  _id: Int!
  name: String!
  friends: [Character]!
  appearsIn: [Episode]!
}

The interface requires a $typeResolver

Tip
for more information on interface mappings see Interface Mappings
{
    "mappings": {
        "Character": {
            "$typeResolver": {
                "Human": "field-exists(homePlanet)",
                "Droid": "field-exists(primaryFunction)"
            }
        }
    }
}

object Starship

The GraphQL schema defines the object type Starship:

type Starship {
  _id: Int!
  name: String!
  length(unit: LengthUnit = METER): Float
}

No mapping is required since default field-to-field mappings are fine

Tip
for more information on field-to-field mappings see Field-to-field mapping

objects Human and Droid

The GraphQL schema defines the object types Human and Droids:

type Human implements Character {
  _id: Int!
  name: String!
  friends: [Character]!
  appearsIn: [Episode]!
  homePlanet: String!
}

type Droid implements Character {
  _id: Int!
  name: String!
  friends: [Character]!
  appearsIn: [Episode]!
  primaryFunction: String!
}

Those object types have the field friends in common, actually derived by the fact that they both implement the interface Character. This field requires a field-to-query mapping.

Tip
for more information on field-to-query mappings see Field-to-query Mapping
Tip
for more information on the $fk operator see Mapping Operators
{
    "mappings": {
        "Human": {
            "friends": {
                "db": "restheart",
                "collection": "star-wars-characters",
                "find": { "_id": { "$in": { "$fk": "friends" } } }
            }
        },
        "Droid": {
            "friends": {
                "db": "restheart",
                "collection": "star-wars-characters",
                "find": { "_id": { "$in": { "$fk": "friends" } } }
            }
        }
    }
}

Query

The GraphQL schema defines the queries hero and search

type Query {
  hero(episode: Episode!): Character
  search(text: String!): [SearchResult]
}

Queries always require mappings.

Tip
for more information on query mappings see Field-to-query Mapping
{
    "mappings": {
        "Query": {
            "hero": {
                "db": "restheart",
                "collection": "star-wars-characters",
                "find": { "heroOf": { "$arg": "episode" } }
            },
            "search": {
                "db": "restheart",
                "collection": "star-wars-characters",
                "find": { "name": { "$regex": { "$arg": "text" } , "$options": "i" } } }
        }
    }
}

The full GraphQL Schema

union SearchResult = Human | Droid | Starship

enum LengthUnit {
  METER
}

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

type Starship {
  _id: Int!
  name: String!
  length(unit: LengthUnit = METER): Float
}

interface Character {
  _id: Int!
  name: String!
  friends: [Character]!
  appearsIn: [Episode]!
}

type Human implements Character {
  _id: Int!
  name: String!
  friends: [Character]!
  appearsIn: [Episode]!
  homePlanet: String!
}

type Droid implements Character {
  _id: Int!
  name: String!
  friends: [Character]!
  appearsIn: [Episode]!
  primaryFunction: String!
}

type Query {
  hero(episode: Episode!): Character
  search(text: String!): [SearchResult]
}

The full GraphQL APP definition

{
    "_id": "star-wars",
    "descriptor": {
        "name": "star-wars",
        "description": "GraphQL application used in the Star Wars Tutorial",
        "enabled": true,
        "uri": "star-wars"
    },
    "schema": "union SearchResult = Human | Droid | Starship enum LengthUnit { METER } enum Episode { NEWHOPE EMPIRE JEDI } type Starship { _id: Int! name: String!  length(unit: LengthUnit = METER): Float } interface Character { _id: Int! name: String! friends: [Character]! appearsIn: [Episode]! } type Human implements Character { _id: Int! name: String! friends: [Character]! appearsIn: [Episode]! homePlanet: String! } type Droid implements Character { _id: Int! name: String! friends: [Character]! appearsIn: [Episode]! primaryFunction: String! } type Query { hero(episode: Episode!): Character search(text: String!): [SearchResult] }",
    "mappings": {
        "Episode": { "NEWHOPE": 4, "EMPIRE": 5, "JEDI": 6 },
        "SearchResult": {
            "$typeResolver": {
                "Human": "field-exists(homePlanet)",
                "Droid": "field-exists(primaryFunction)",
                "Starship": "field-exists(length)"
            }
        },
        "Character": {
            "$typeResolver": {
                "Human": "field-exists(homePlanet)",
                "Droid": "field-exists(primaryFunction)"
            }
        },
        "Human": {
            "friends": {
                "db": "restheart",
                "collection": "star-wars-characters",
                "find": { "_id": { "$in": { "$fk": "friends"} } }
            }
        },
        "Droid": {
            "friends": {
                "db": "restheart",
                "collection": "star-wars-characters",
                "find": { "_id": { "$in": { "$fk": "friends"} } }
            }
        },
        "Query": {
            "hero": {
                "db": "restheart",
                "collection": "star-wars-characters",
                "find": { "heroOf": { "$arg": "episode" } }
            },
            "search": {
                "db": "restheart",
                "collection": "star-wars-characters",
                "find": { "name": { "$regex": { "$arg": "text" } , "$options": "i" } } }
        }
    }
}