GraphQL API Tutorial with RESTHeart
This tutorial guides you through the process of implementing a GraphQL API using RESTHeart, following the structure and concepts presented in the GraphQL.org tutorial on Star Wars characters and episodes, demonstrating the implementation using RESTHeart.
Dive into the world of GraphQL and RESTHeart, and may the force be with your data!
Note
|
While this tutorial provides buttons for executing requests using "Rest Ninja," please be aware that it may not seamlessly function with Safari due to HTTPS requirements. For a smoother experience, we recommend using Chrome or Firefox browsers (refer to Configure TLS). |
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 just requires to define a json configuration, known as GraphQL App Definition and store 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 using docker, please refer to 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.
PUT /star-wars-characters HTTP/1.1
Create the gql-apps collection. This is where the GraphQL App definition will be inserted.
PUT /gql-apps HTTP/1.1
3 Insert data
Create documents in the star-wars-characters collection:
POST /star-wars-characters?wm=upsert HTTP/1.1
Request body
[
{"_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}
]
4 Insert the GraphQL App Definition
To insert the GraphQL app definition, run the following:
POST /gql-apps?wm=upsert HTTP/1.1
Note
|
Request body omitted because is quite long. Find it in the The full GraphQL APP definition section. |
Execute queries
hero
POST /graphql/star-wars HTTP/1.1
Request body
{
hero(episode: JEDI) {
name
friends {
name
}
... on Droid {
primaryFunction
}
... on Human {
homePlanet
}
}
}
{
"data": {
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
],
"primaryFunction": "Astromech"
}
}
}
Note
|
Please note that the response content-type is application/graphql-response+json . It is treated as a binary format by RestNinja, which results in a less user-friendly display. To circumvent this issue and view the response more effectively, you can opt to use HTTPie, a command-line HTTP client:
|
$ echo '{ hero(episode: JEDI) { name friends { name } ... on Droid { primaryFunction } ... on Human { homePlanet } } }' | http -a admin:secret :8080/graphql/star-wars Content-Type:application/graphql
search
POST /graphql/star-wars HTTP/1.1
Request body
{
search(text: "an") {
... on Character {
name
}
... on Starship {
name
length
}
}
}
{
"data": {
"search": [
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
},
{
"name": "TIE Advanced x1",
"length": 9.2
}
]
}
}
Note
|
Please note that the response content-type is application/graphql-response+json . It is treated as a binary format by RestNinja, which results in a less user-friendly display. To circumvent this issue and view the response more effectively, you can opt to use HTTPie, a command-line HTTP client:
|
$ echo '{ search(text: "an") { ... on Character { name } ... on Starship { name length } } }' | http -a admin:secret :8080/graphql/star-wars Content-Type:application/graphql
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" } } }
}
}
}