GraphQL API
Overview
The GraphQL plugin works side by side with the already existing REST endpoints to get a managed unified GraphQL API to build modern applications.
The restheart-graphql
service added to RESTHeart exposes a read-only (no mutations and subscription) GraphQL API for inquiring MongoDB resources.
Configuration
For each GraphQL application you need to define a so called GraphQL app definition. This is a JSON document to create in a MongoDB collection. You can specify the collection that holds the app definitions in the RESTHeart’s configuration as follow:
plugins-args:
graphql:
enabled: true
secured: false
uri: /graphql
db: <db_name>
collection: <reserved_collection_name>
verbose: false
by default:
-
<db_name>
= restheart -
<reserved_collection_name>
= gql-apps
This kind of configuration allows you to change dynamically the behavior of your GraphQL application by updating the related document on MongoDB.
GraphQL App Definition
A GraphQL application definition is composed by three sections:
{
"descriptor": "...",
"schema": "...",
"mappings": "..."
}
Descriptor
Here you can specify:
-
name
: GraphQL application name. -
description
: GraphQL application description. -
enabled
: can betrue
orfalse
. If it’sfalse
, the GraphQL application can’t be queried and vice-versa. By default it istrue`
. -
uri
: it specifies at which endpoint your GraphQL application is reachable (e.g./graphql/uri
). If you don’t specify the URI, application’s name is used instead (so, at least one ofname
orURI
must be present).
{
"descriptor": {
"name": "MyApp",
"description": "my first test GraphQL application",
"enabled": true,
"uri": "myapp"
},
"schema": "...",
"mappings": "..."
}
Schema
This section must contain GraphQL application’s schema written with Schema Definition Language (SDL). For example:
{
"descriptor": "...",
"schema": "type User{name: String surname: String email: String posts: [Post]} type Post{text: String author: User} type Query{users(limit: Int = 0, skip: Int = 0)}",
"mappings": "..."
}
Note
|
In order the schema to be a valid, it must contain the type Query. |
Mappings
In this section you can specify how GraphQL types fields are mapped on MongoDB data. Mappings have to be organized following the same hierarchical structure of GraphQL SDL schema, so for each GraphQL type you can insert a JSON object with a property for each field that you want to map.
Two kinds of mapping can be made:
-
Field to Field mapping
-
Field to Query mapping
-
Field to Aggregation mapping (*)
Note
|
(*) the new Field to Aggregation mapping is only available on snapshot release and foreseen for future releases. |
Field to Field mapping
You can map a GraphQL field with a specific MongoDB document field or with an element of a MongoDB array by dot-notation. For instance:
{
"descriptor": "...",
"schema": "...",
"mappings":{
"User": {
"name": "firstName",
"phone": "contacts.phone",
"email": "contacts.emails.0",
},
"Post": "...",
"Query": "..."
}
}
Whit this configuration:
-
name
is mapped with MongoDB documentfirstName
field -
phone
is mapped with fieldphone
incontacts
nested document -
email
is mapped with 1st element ofemails
array withincontacts
nested document
Notice that, if you don’t specify a mapping for a field, RESTHeart will map it with a MongoDB document field with the same name.
Field to Query mapping
You can map a GraphQL field with a MongoDB query using the following parameters:
-
db
(String): database name; -
collection
(String): collection name; -
find
(Document): selection filter using query operators (e.g.$in
,$and
,$or
, …); -
sort
(Document): order in which the query returns matching documents; -
skip
(Document or Integer): how many documents should be skipped of those resulting; -
limit
(Document or Integer): how many documents should be returned at most of those resulting.
Note
|
Starting from v6.5.1, unlimited queries are not allowed: if the query does not specifies a limit , the service configuration default-limit is applied. Also the limit cannot exceed the max-limit . The default GraphQL service configuration in restheart.yml follows:
|
plugins-args:
graphql:
uri: /graphql
db: restheart
collection: gql-apps
# default-limit is used for queries that don't not specify a limit
default-limit: 100
# max-limit is the maximum value for a Query limit
max-limit: 1000
verbose: false
Moreover, a query is parametric when the mapped MongoDb query includes one or more $arg
and $fk
operators:
-
$arg
: allows to use the arguments of the GraphQL query in the MongoDb query; -
$fk
: allows to map a GraphQL field with a MongoDB relation, specifying which is the document field that holds the relation.
For example, having the following GraphQL schema:
type User {
id: Int!
name: String
posts: [Post]
}
type Post {
id: Int!
text: String
category: String
author: User
}
type Query {
usersByName(_name: String!, _limit: Int = 0, _skip: Int = 0): [Users]
}
with MongoDB data organized in the two collections users
and posts`
:
USERS
{
"_id": {"$oid": "6037732f5fa7d52581015ed9" },
"firstName": "Foo",
"lastName": "Bar",
"contacts": { "phone": "+39113", "emails": ["foo@domain.com", "f.bar@domain.com"],
"posts_ids": [ { "$oid": "606d963f74744a3fa6f4489a" }, { "$oid": "606d963f74744a3fa6f4489e" } ] }
}
POSTS
[
{ "_id": {"$oid": "606d963f74744a3fa6f4489a" },
"text": "Lorem ipsum dolor sit amet",
"category": "front-end",
"author_id": {"$oid": "6037732f5fa7d52581015ed9" }
},
{ "_id": {"$oid": "606d963f74744a3fa6f4489e" },
"text": "Lorem ipsum dolor sit amet",
"category": "back-end",
"author_id": {"$oid": "6037732f5fa7d52581015ed9" }
}
]
then, possible mappings are:
{
"descriptor": "...",
"schema": "...",
"mappings": {
"User": {
"posts": {
"db": "restheart",
"collection": "posts",
"find": { "_id": { "$in": { "$fk": "posts_ids" } } }
}
},
"Post": {
"author": {
"db": "restheart",
"collection": "user",
"find": { "_id": { "$fk": "author_id" } }
}
},
"Query": {
"usersByName": {
"db": "restheart",
"collection": "users",
"find": { "name": { "$arg": "_name" } },
"limit": { "$arg": "_limit" },
"skip": { "$arg": "_skip" },
"sort": { "name": -1 }
}
}
}
}
As result, we are saying that:
-
given a
User
, his posts are the MongoDB documents, within theposts
collection, with value of field_id
that falls in theposts_ids
array of `User’s document; -
given a
Post
, its author is the MongoDB document, within theusers
collection, with value of field_id
equal toauthor_id
of `Post’s document; -
asking for
userByName
GraphQL field, the MongoDB documents searched are the ones within theusers
collection with fieldname
equal to value of_name
GraphQL argument. Moreover, we are asking to return at most_limit
documents, to skip the firsts_skip
ones and to sort them by name in reverse order.
Note
|
you can use also the dot notation with the $fk operator.
|
Field to Aggregation mapping
You can map a GraphQL field with a MongoDB query using the following parameters:
-
db (String): database name;
-
collection (String): collection name;
-
stages (Array): array of aggregation stages.
As with field to query mapping, $arg
and $fk
operators are allowed in aggregation stages.
Referring to the previous example of mapping, the following aggregation stages are possible:
...,
"Query": {
....,
"countPostsByCategory": {
"db": "restheart",
"collection": "users",
"stages": [
{ "$group": { "_id": "$category", "count": { "$count": {} } } }
]
}
}
And the Query in the GraphQL schema will now have the following field:
type Stats {
_id: String
count: Int
}
type Query {
countPostsByCategory: [Stats]
}
Bson types
All primitive GraphQL types have been mapped to corresponding BSON types plus a set of custom GraphQL scalars types have been added:
GraphQL type |
Bson Type |
Example |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Example
The following GraphQL type User
defines the property _id
to be of type ObjectId
type User {
_id: ObjectId
name: String
surname: String
email: String
posts: [Post]
}
Queries
Up to now, only GraphQL Query can be made, so no subscription or mutation. In order to make a query you can use HTTP request with POST method and both content-type application/json
and application/graphql
. For instance:
application/json
POST /graphql/<app-uri> HTTP/1.1
Host: <host-name>
Content-Type: application/json
Request body
{
"query": "query test_operation($name: String){ userByName(_name: $name){name posts{text}} }",
"variables": { "name": "..." },
"operationName": "..."
}
application/graphql
POST /graphql/<app-uri> HTTP/1.1
Host: <host-name>
Content-Type: application/graphql
Request body
{
userByName(_name: "...") {
name
posts {
text
}
}
}
Limitations
The GraphQL service has the following limitations:
-
Read-only API: mutations are not supported; the GraphQL API is only intended for simplifying data fetching. To write data, the REST API must be used.
-
Not-supported schema keywords: the schema resolvers do not support the following keywords:
enum
,union
,interface
andinput
.
Response codes
In the following table are reported possible RESTHeart GraphQL Service responses:
HTTP Status code |
description |
200 |
It’s all OK! |
400 |
Invalid GraphQL query (e.g. required fields are not in the schema, argument type mismatch), schema - MongoDB data type mismatch, invalid app definition |
401 |
Unauthorized |
404 |
There is no GraphQL app bound to the requested endpoint |
405 |
HTTP method used not supported |
500 |
Internal Server Error |
Example responses
200 - OK
{
"data":{
"userByName":[
{
"firstName": "nameUser1",
"lastName": "surnameUser1"
},
{
"firstName": "nameUser2",
"lastName": "surnameUser2"
}
]
}
}
400 - Bad Request - Invalid GraphQL Query / schema - MongoDB data type mismatch
{
"data": "...",
"errors" : "..."
}
400 - Bad Request - Invalid GraphQL App Definition
{
"http status code": 400,
"http status description": "Bad Request",
"message": "..."
}
405 - Method Not Allowed
{
"http status code": 405,
"http status description": "Method Not Allowed"
}
500 - Internal Server Error
{
"http status code": 500,
"http status description": "Internal Server Error"
}