HomeLearnHow-toRealm GraphQL Demo: Custom Resolvers

Realm GraphQL Demo: Custom Resolvers

Updated: May 25, 2021 |

Published: May 25, 2021

  • Realm
  • GraphQL

By Josman Pérez Expóstio

Rate this article

#Motivation

Realm offers several simple ways to provide third-party access to our data. One of them can be through the so-called Webhooks. Through these, we could offer in a controlled way different endpoints to access our services and therefore our data.

A real example would be to provide such webhooks to customers or partners so that they can access certain information necessary for the business logic existing within that particular use case.

However, this way of solving this problem has certain disadvantages. The most important ones include:

  1. Fixed-size for the response payload: The size of the response cannot exceed 4 MB. This limitation would prevent us from being able to query aggregated data that would require a response larger than 4 MB.
  2. Creation of different endpoints for each business logic requirement. This would not only pose a management problem, but also a maintenance problem: what if we make changes in the future? How do we manage the different versions? This model becomes more complex if we want to send parameters in the request to tailor the response to the requirements.

So what could be a more efficient solution?

Realm offers the possibility to use its GraphQL API to cover this and other needs. The advantages of using GraphQL for this use case could be listed as:

  1. An easier and simpler method to execute accurate endpoint calls.
  2. Easier to choose what we need in the response, alleviating the size of the response by not requiring unnecessary fields.
  3. Simpler maintenance.
  4. Avoid versioning.

#What are we going to build?

Making use of the sample available datasets in MongoDB. We will build a filter to be able to return those movies that meet a set of requirements. These requirements are:

  1. Having a given IMDB rating.
  2. Belonging to a set of genres.
  3. Being a certain rate.
  4. Being in several available languages.

The filtering parameters will be dynamic so that we can return those that best fit our criteria.

For this, we will use a GraphQL Custom Resolver and an external client application to execute our queries.

#Prerequisites

This tutorial will provide a step-by-step guide to run the demo. To do this we must follow these prerequisites:

  1. Have a Cloud MongoDB account.
  2. Create a Free Tier Cluster.
  3. Configure realm-cli .
  4. Add an API-Key to be able to access using realm-cli.
  5. Load initial data into the Cluster.

#Create an Atlas Account

To begin, you’ll need a MongoDB Atlas account. If you’ve already got an existing MongoDB Atlas Account, you can skip this step and jump to Install the Realm CLI section. If you don’t have an Atlas account, follow the steps below to create one:

  1. Navigate to the MongoDB Atlas login page.
  2. Click Login.
  3. Either enter a new set of user credentials or click the Sign Up with Google button.
  4. Click Sign Up to create your account.
  5. Follow the prompts to create an organization and project in your Atlas account. You can use the default suggested names or enter your own.

Account startup screenshot

When you finish creating your organization and project, you should end up on a screen that prompts you to create an Atlas cluster:

Free Tier Cluster screenshot

#Create a Free Atlas Cluster

Next, you’ll need a MongoDB Atlas cluster running MongoDB 4.4 or higher. If you’ve already created a free cluster in your Atlas project running a version of MongoDB other than 4.4, you can create a new project in Atlas and then create a new cluster running MongoDB 4.4 in that project using the instructions below. If you haven’t created any clusters yet, follow the instructions below to create your first free cluster:

  1. Log into your MongoDB Atlas account at cloud.mongodb.com.
  2. Once you’re logged into your account, Atlas should prompt you to create your first cluster. In the Shared Clusters category, click Create a Cluster. Alternatively, you can click Build a Cluster from the project view in your Atlas account.
  3. Under Cloud Provider & Region, select AWS and N. Virginia (us-east-1).
  4. Under Additional Settings, select MongoDB 4.4 from the Select a Version dropdown.
  5. Under Cluster Name, enter the name Cluster0 for your new cluster.
  6. Click the Create Cluster button at the bottom of the page.

After creating your cluster, Atlas should launch the project view for your Atlas account. In this view, you’ll see Atlas’s progress as it initializes your new cluster:

Screenshot of a recently created cluster

#Install the Realm CLI

Now that you’ve created a cluster to use as the data source for your Realm app, we need some way to create the app itself. In most cases, you’d use the Realm UI, which you can access through the Atlas UI. However, for this tutorial, we’re going to use the Realm Command Line Interface, also known as realm-cli.

We’re using the Realm CLI because it allows you to manage your Realm apps programmatically using JSON configuration files instead of the Realm UI.

This lets you get started with a pre-prepared app configuration faster. Follow the instructions below to install the Realm CLI in your development environment using either a package manager or the realm-cli binary:

Realm CLI is available on npm. To install it on your system, ensure that you have Node.js installed and then run the following command in your shell:

1npm install -g mongodb-realm-cli@beta

After installing the realm-cli, you can run the following command to confirm that your installation was successful:

1realm-cli --version

If you see output containing a version number such as 2.0.0-beta.4, your realm-cli installation was successful.

#Add an API Key to Your Atlas Project & Log into the Realm CLI

Now that you’ve got realm-cliinstalled to your development environment, you’ll need a way to authenticate using realm-cli. For security reasons, realm-clionly allows login using a programmatic API key, so we’ll begin by creating a programmatic API Key that you can use to administrate your new Atlas project:

  • Click Access Manager at the top of the Atlas UI. Select the Project Access option from the dropdown.
  • Navigate to the API Keys tab.
  • Click the Create API Key button.
  • In the Description text box, enter “API Key for the MongoDB Realm CLI”.

Create API key screenshot in Realm UI

  • In the Project Permissions dropdown, select “Project Owner” and deselect “Project Read Only”.

Project Permissions screenshot

  • Copy your Public API Key and save it somewhere.
  • Click Next.
  • Copy your Private API Key and save it somewhere; after leaving this page, you will no longer be able to view it via the Realm UI.
  • Click the Add Access List Entry button.
  • Click Use Current IP Address.
  • Click Save.
  • When you have safely recorded your private API key, click Done to navigate back to the Project Access Manager page.
  • Use the following command in your terminal to authenticate with the Realm CLI:
1realm-cli login --api-key <public API key> --private-api-key <private API key>

If realm-cli produces output like the following, you have successfully authenticated:

1you have successfully logged in as <public API key>

#Load sample dataset to the Cluster

Once we have deployed the Cluster, we can make use of the sample collections that MongoDB provides. To do this, we must click on “…” and “Load Sample Dataset”.

Load Sample Dataset screenshot

This process may take a few minutes due to the size of the sample collections. They are approximate ~350 MB. Once it has finished, we can verify, by clicking on collections, that the sample_mflix.movies the collection has been loaded successfully.

One document of Movie collection screenshot

Another way to check that the data has been loaded into our Cluster and interact with it can be by installing MongoDB Compass or accessing it through a terminal and Mongo shell.

#Adding Rules to our collections

To use GraphQL, we need to configure rules for the collections that we are going to use, to do so we go to DATA ACCESS and RULES in Realm UI.

We then need to select the newest imported sample_mflix.moviescollection and click on “Configure Collection”. We can configure it without selecting any Template (in the future we can make changes to it).

Rules section in Realm UI

Once the collection is selected and after clicking “Configure Collection”, we must select the “All Additional Fields” option and check the “Read” box. At this point, this will allow any authenticated user to read the data in this collection. This will be necessary to be able to later make requests through external clients such as Postman.

Rules section in Realm UI

After this step, we can configure the Schema. To do this and once in the “Schema” tab, we can use the documents already loaded in the collection for Realm to generate a schema from them.

Generate Schema in Realm UI

After waiting a few minutes, we will be able to consult the Schema created on this same screen.

Schema generated for the movies collection

#Add an authentication provider

All the requests that we are going to make through our GraphQL client must be authenticated. For this, we can activate any of the authentication providers available in Realm.

For this example, we will use API Keys, this way we could create an API Key for each of our clients/partners and disable access in the future if needed.

Once the authentication provider has been activated, we generate a new key and copy the value provided by the interface.

API Key Authentication Provider

#Testing GraphQL with GraphiQL

Realm provides an embedded GraphiQL interface to test requests directly.

These requests do not need to be authenticated and allow you to test the requests before making them in a real scenario.

To check that everything is configured correctly, we can perform the following Query.

1query {
2 movie {
3 _id
4 }
5}

This request is a simple Query to the movies collection where we are requesting to return just the _id. From a MongoDB point of view, this request could be equated to the following mongoshell method:

1db.movies.findOne({},{"_id" : 1})

One of the advantages of using the GraphQL API in Realm is that it generates the Schema automatically for the configured collections. We can check this by navigating to the “Schema” tab in GraphQL and verifying that indeed the schema for the “movies” collection is generated. At this point, we can download the Schema for later use in a third-party GraphQL client such as Postman.

#Create a custom resolver

Inthe description of our problem, we talk about how we need to detect in our dataset movies that correspond to a set of predefined filters. Up to this point, we could get all the movies from the dataset and perform some processing in a client application, but fortunately, we can make use of the aggregation pipeline in MongoDB to perform this transformation on the server through a custom resolver.

If we want to test the syntax needed to get all movies that match our filter we must perform this aggregation:

1[{"$match": {
2 "imdb.rating": { "$gte": 7 },
3 "genres": { "$nin": [ "Crime", "Horror" ] } ,
4 "rated": { "$in": ["PG", "G" ] },
5 "languages": { "$all": [ "English", "Japanese" ] }
6 }
7}]

The first step to perform is to “Add a Custom Resolver” in GraphQL.

Screenshot of Add Custom Resolver Screen in Realm

The fields to be filled in are the following:

  • GraphQL Field Name: This corresponds to the name we want to use when we refer to this custom resolver in our queries.
  • Parent Type: Type of custom resolver we are creating, in our case, being a read request, we will select Query.
  • Function: Here we create the function that will be called every time a call to our custom resolver is executed. You can link an existing function or create it here.

The code of the function would be the following:

1exports = async function() {
2 const request = context.services.get('mongodb-atlas').db('sample_mflix').collection("movies");
3
4 const pipeline = [
5 {
6 "$match": {
7 "imdb.rating": { "$gte": 7 },
8 "genres": { "$nin": [ "Crime", "Horror" ] } ,
9 "rated": { "$in": ["PG", "G" ] },
10 "languages": { "$all": [ "English", "Japanese" ] }
11 }
12 }];
13
14 return await request.aggregate(pipeline).toArray()
15 .then(data => {
16 console.log(data.length);
17 return data;
18 })
19 .catch(err => {
20 console.log(err.toString());
21 return err.toString();
22 });
23};

Screenshot of the Custom Resolver editor in Realm UI

  • Input Type (Recommended): At the moment we will leave it at None. Later we will explain what we can do with it and what it is used for.
  • Payload Type (Recommended): Type of object of the response. In our case, our aggregate will return a set of movies, therefore we choose Existing Type (List) and type [Movie].

At this point, we can test our new custom resolver directly in Realm. To do this, in the same GraphiQL section we can write our query as follows:

1query {
2 oneTitleMovies {
3 title
4 }
5}

After clicking the Play button we should see the results of our query.

https://mongodb-devhub-cms.s3.us-west-1.amazonaws.com/0_2_Og_Ewm_Vo_T_Mw_GR_8_YE_3a6eca2987

#Testing our newest created Custom Resolver in Postman

To test our query in an external client we will use Postman. There is a tutorial to make queries in Postman with an API/Schema. In this example, we will make a simple query without a schema and therefore we will not use it (but it is worth a look).

To test this query in Postman, we will create a new POST request where the URL is provided by Realm (GraphQL Endpoint). In the body of the request, we will select GraphQL and write:

1query {
2 oneTitleMovies {
3 title
4 cast
5 }
6}

When working with Realm and an external GraphQL client, we need to add some kind of authentication. In a previous step, we created an API Key as an authentication provider, although we can use any of them.

In the request headers, we should add:

1{"apiKey","{{api_key}}"}

Where we replace “{{api_key}}” with the value obtained previously.

A general idea of what Postman’s cURL for this request would look like is:

1curl --location --request POST '[your_graphql_endpoint]' \
2 --header 'apiKey: [your_api_key]' \
3 --header 'Content-Type: application/json' \
4 --data-raw '{"query": "query {"query oneTitleMovies {"title" {"cast", "variables":{}}'

Postman request for a GraphQL query

#Add an Input Type to our Custom Resolver

When we created our custom resolver, we associated it to a function. This function had some preset parameters:

  1. “imdb.rating”: { $gte: 7 }
  2. genres: { $nin: [ “Crime”, “Horror” ] } ,
  3. rated: { $in: [ “PG”, “G” ] },
  4. languages: { $all: [ “English”, “Japanese” ] } }

We can create an Input Type of type Custom Type so that those values that are fixed for the moment can be sent by parameters. Custom Types must be defined as a Schema in a JSON object. For our use case, our Schema will be the following:

1{
2 "bsonType": "object",
3 "title": "Filter",
4 "required" : [
5 "imdbRating",
6 "genres",
7 "rated"],
8 "properties": {
9 "imdbRating": {
10 "bsonType": "int"
11 },
12 "genres": {
13 "bsonType": "array",
14 "items": {
15 "bsonType": "string"
16 }
17 },
18 "rated": {
19 "bsonType": "array",
20 "items": {
21 "bsonType": "string"
22 }
23 },
24 "languages": {
25 "bsonType": "array",
26 "items": {
27 "bsonType": "string"
28 }
29 }
30 }
31}

We will make the “IMDB.rating”, “genres” and “rated” fields mandatory so that they have to be always sent and the “languages” field will be optional.

Input Type of Custom Type in Custom Resolver

To be able to use the data sent by our new Input Type,we must modify the linked function so that we can receive them by parameters.

1exports = async function({imdbRating, genres, rated, languages}) {
2
3 const request = context.services.get('mongodb-atlas').db('sample_mflix').collection('movies');
4
5 const lang = languages === undefined ? ["English", "Japanese"] : languages;
6
7 const pipeline = [
8 {
9 $match: {
10 "imdb.rating": { "$gte": imdbRating },
11 "genres": { "$nin": genres } ,
12 "rated": { "$in": rated },
13 "languages": { "$all": lang }
14 }
15 }];
16
17 return await request.aggregate(pipeline).toArray()
18 .then(data => {
19 console.log(data.length);
20 return data;
21 })
22 .catch(err => {
23 console.log(err.toString());
24 return err.toString();
25 });
26};

Since we know that “imdbRating”, “genres” and “rated” are mandatory, we can assume that they will always come as parameters and therefore we assign them directly to our aggregation. For the “languages” field as it is optional, we will have to verify that there is indeed an associated value and if not we will send default values.

Now we can test this query in our external GraphQL client. The query would look like this (to get the same results as at the beginning):

1query {
2 oneTitleMovies(input: {
3 imdbRating: 7
4 genres: [
5 "Crime"
6 "Horror"
7 ]
8 rated: [
9 "PG"
10 "G"
11 ]
12 languages: [
13 "English"
14 "Japanese"
15 ]
16 }) {
17 title
18 }
19}

From here we can play with the different fields of our input to filter our results. One of the advantages of using GraphQL as a replacement for a Rest API is that the fields or response values can be selected in advance. In our example above, we are only returning the “title” field, but we could return a subset of all the fields in the “Movies” collection.

#Wrapping up

Realm GraphQL is a powerful tool to create serverless applications that can easily cover all your basic and complex use cases. Using Realm as a BaaS can help you build and deploy applications faster than ever.

In this tutorial, we have learned how to create a custom resolver linked to a function to resolve an aggregation pipeline. You can simply adapt this example to your own complex use case.

Questions? Comments? We'd love to connect with you. Join the conversation on the MongoDB Community Forums.

#Download example code from GitHub

You can download the sample code from here and import it into your Realm application with

1realm-cli import \
2 --app-id=myapp-abcde \
3 --path=./path/to/app/dir \
4 --strategy=merge \
5 --include-hosting \
6 --include-dependencies
Rate this article
MongoDB Icon
  • Developer Hub
  • Documentation
  • University
  • Community Forums

© MongoDB, Inc.