GraphQL: The Easy Way to Do the Hard Stuff
Rate this tutorial
is rapidly growing in popularity as the new standard for working with APIs, and it's easy to see why! Performant and efficient, this groundbreaking API query language gives developers a single API endpoint to access exactly the data they need. Relationships and custom resolvers allow you to even further evolve your GraphQL API to access multiple data sources. With such extensibility, you can iterate even more quickly on front-end features your application users will love.
In this tutorial, we'll walk through a comprehensive example starting only with data and build out an entire site using GraphQL - showing why it's become so popular.
We'll start by setting up a hosted, serverless, and secure GraphQL API for our data in minutes. Then we will extend that API with relationships to other data collections and custom resolvers in order to bundle together different data sources and APIs together under the same schema. Finally, we will build out a web application using our GraphQL endpoint to demonstrate how GraphQL gives us exactly the data we want - nothing more, nothing less.
Along the way, we'll learn how to
- automatically generate JSON schema for your MongoDB collections.
- create types and resolvers for that data.
- define custom query resolvers to access other databases or 3rd party APIs.
- test your schema using the GraphiQL interface inside the Atlas UI.
Do you prefer to learn by video? To see us build out our GraphQL application in real time, check out this video tutorial.
- The sample_analytics.customers collection contains customer details including account numbers.
- The sample_analytics.accounts collection contains the details of each of the customer's accounts. Each document contains an account_id and the products that a customer has purchased.
In the below image, notice how the value in the customer document's accounts array points to the account_id field in the account document.
Step 1. Create the BankingDogs Application
MongoDB Atlas App Services, simply stated, is a serverless application backend that streamlines your backend code so you can focus on front-end features. We'll use App Services in this tutorial to not only offload the GraphQL backend and middleware layers, but also to handle the authentication, schema creation, and GraphQL queries.
Inside your Atlas UI, select the App Services tab and Build your own App.
Name your application and link it to your Atlas cluster with the sample data loaded. I named my application BankingDogs. You'll see why very soon.
Then click Create App Service. Now we are well on our way!
Step 2: Define Data Access Rules for Your Collections
App Services enforces data collection rules for all incoming GraphQL requests. All GraphQL requests must include an authentication token to identify the logged-in App Services user making the request. App Services evaluates a role for every document included in a GraphQL operation and only returns fields and documents that the App Services user has permission to see.
We'll examine this more in detail later, but for right now select the Rules menu under Data Access on the left. The select the sample_analytics and accounts collection. Select readAll and Add present role. This preset role will apply to every logged-in user and will allow all fields to be read.
GraphQL is strongly typed. Every GraphQL API conforms to a "schema." This schema data model serves as a contract between the client and the server to define what fields are mapped to what types and what kinds of queries a client can make. As such, schemas need to be created for any fields we want to expose via the API. In the upcoming Steps 3-6, we will create the schema from our data, test the schema, and extend the schema through relationships to other data collections. Finally in Steps 7-8, we will extend our schema beyond simply our MongoDB data to include 3rd party data by creating custom resolvers.
Step 3: Create the Schema for Accounts Collection
Switch to the Schema tab, select the sample_analytics and accounts collection, click Generate a Schema and Generate Schame from sampling. Once your schema is generated, to ensure it is valid, click Run Validatation. Save Draft, then Review Draft & Deploy the changes.
And that's it!! You just created your first GraphQL schema and endpoint. At this point, you have done all you need to do to surface your MongoDB Atlas data to client applications. Even if you do not have a front end, you can still test your endpoint and query for your data inside the App Services interface.
Pro Tip! To speed up development, you can bypass the Review & Deploy changes step by disabling drafts. You'll find the setting under Deployment > Configuration.
Step 4: Test New Schema and API in GraphiQL
There are several exciting things to note here:
- You have your GraphQL endpoint to use in your application.
- You have your GraphiQL interface where you can test out queries.
- You have automatically generated documentation in the Documentation Explorer.
A favorite aspect of GraphQL is that it's self documenting. You'll find the Documentation Explorer on the right side of the GraphiQL interface. Click into Query and you'll see the Query fields that are now available:
account and accounts were created for us when we generated this schema for that collection. Finally, click into account to see the fields that are returned: _id, account_id, limit, and products.
GraphiQL provides an invaluable tool which will help you structure your GraphQL queries correctly. Switch to the query window inside of GraphiQL and scroll to the bottom (or just delete the commented help text). You can see our first query to return an account document. Clicking RUN will execute this query and see the response on the right.
Feel free to peruse the Document Explorer and play around with your queries in GraphiQL.
Step 5: Create a Custom Schema for Customers Collection
Now that you have successfully created and tested a GraphQL API endpoint for your accounts data, let's repeat these same steps to create and validate a schema for the customers collection. Starting with the Rules, click on the customers collection in the left side menu and again add the readAll preset role.
For the schema, we're going to define our own, as we are not interested in the tier_and_details field. After dicking Generate a Schema, select the option or, skip and manually define your own schema:
Paste in the following schema object which simply removes the tier_and_details field:
Now you can validate and save with no problems. Be sure to Review & Deploy the changes.
Going back to GraphiQL, you can start querying for the customer data right away:
Notice you have the EXACT SAME ENDPOINT as you used for the accounts collection! You can also combine calls to both collections, thereby avoiding multiple roundtrips to the server.
Request as many or as few fields as you wish to get exactly the data you want. This eliminates over-fetching, decreases the response payload, and avoids long page load times. In turn, you get better application performance and can write cleaner code.
We can use our schema to define relationships that connect each document in one local collection to one or more documents in a different, or foreign, collection on the linked MongoDB Atlas cluster. So let's connect our customers to their accounts. Creating a relationship between customers and accounts allows us to fluently query related documents in GraphQL read and write operations. In our case, this will enable us to query for a customer and not just get the customer information, but also the list of products for each of their accounts from the accounts collection in one call.
Step 6: Define a Relationship Between Customers and Accounts
To create a relationship, return to Schema for the customers collection Remember how our customers and accounts collections are related through the customer's accounts array pointing to the account_id field in the accounts collection? Let's link them in the interface. Click + Add Relationship.
This is our relationship defintion:
App Services evaluates which account documents are related to a given customer document based on a linked field key value. Our relationship defines the Parent Field field as accounts (from the customers collection) and the Linked Field as account_id" (from the accounts collection). After clicking Add you will see the JSON representation of this relationship:
Our relationship defines the foreign_key field as account_id. Also note that isList definition field is set to true. This indicates that the relationship from one customer is to multiple account documents because the local field accounts contains an array of values.
With this relationship now defined, App Services can fluidly return a customer and all of her accounts in the same GraphQL query. Returning to GraphiQL, note that we no longer query for simply the accounts field from customers. We must request fields from the corresponding accounts document as per the related account_id field in the accounts collection. In the example below, I am retrieving not only the customer information from the customers collection, but also all of her product information from her related accounts in one single trip to the server:
What would traditionally require 2 distinct API calls can now be accomplished with a single call!
One of the many powerful aspects of GraphQL is the ability to resolve a single query to multiple backend resources. From the : Each field on each type is backed by a function called the resolver which is provided by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value.
App Services for the collection fields defined in your schema. However, you may want to define a field that resolves to something outside of the MongoDB database. With , you can bundle together different data sources - whether from other databases or 3rd party APIs - all under the same schema. To extend our GraphQL API even further to suit our use case, let's create a custom resolver to retrieve a random dog image and quote. After all, right now the world needs more dogs and humor.
Step 7: Create the getDogAndQuote Function
Our custom resolver will point to this function for the logic in defining what gets translated into the actual GraphQL API. In our banking application, we not only want to get our customer's account information, but we also need a good laugh nowadays. For this step, we're going to employ the Computer Quote API from which provides a selection of quotes from the field of computer science - because computer science is hilarious!!!
With this in mind, select the Functions menu on the left:
And Create a New Function.
Name the function getDogAndQuote.
Then switch to the Function Editor and replace the sample code with the following:
Feel free to include error handling as you desire :-).
Use the Console at the bottom of the page to click Run to verify that you get a quote and author back successfully from the API:
Even though our customers collection doesn't have the customer's photo, I like to imagine who we are working with, so let's use the Dog API to retrieve a random dog photo. Add the following lines of code to your function and change the return object to include your dog image.
Save then Review & Deploy the function.
Step 8: Create the Custom Resolver
Finally, to create the custom resolver, select the GraphQL menu on the left and then the Custom Resolvers tab:
Click Add a Custom Resolver.
We want to allow our clients to insert a new field into their GraphQL query which will return the dog image and quote. The GraphQL Field Name is the name of the field that calls the custom resolver and the Parent Type determines if the resolver is a query or a mutation. Set the GraphQL Field Name to DogAndQuote, the Parent Type to Query and the Function to the getDogAndQuote function you just created.
Set the InputType to None, as our resolver doesn't require one. Set the Payload Type to Custom Type with the following:
The title "DogAndQuote" will be used to describe the payload in the GraphQL documentation. In summary, when the App Services GraphQL resolver sees the field "DogAndQuote", it will call the function "getDogAndQuote" to get the result.
Save then Review & Deploy the custom resolver.
Switch to the Explore tab to see the result from the DogAndQuote query:
For good measure, let's add some simple customer data to the query:
On the backend, data from multiple sources (MongoDB, QuoteAPI and Dog API) was retrieved with a single query - thanks to App Services' support for Custom Resolvers! Now our client can query GraphQL and get exactly the data they want - nothing more, nothing less.
Query Filters and Variables
Our current GraphQL query is just returning a single customer record. We can apply a filter to the query to get a specific customer. For example:
We can then further abstract this query by pulling the name into a variable:
- Line 2: App Services App ID
- Line 21: GraphQL endpoint
You will also notice on line 5 that we will need login credentials for App Services GraphQL. As mentioned in Step 2, all GraphQL requests must include an authentication token to identify the logged-in App Services user making the request. App Services built-in authentication support makes your app's authentication implementation quick and painless!
Step 9: Enable Anonymous Authentication
In your App Services interface, navigate over to App Users in the left side menu. In the Authentication Providers tab, toggle Allow users to log in anonymously to on:
The Anonymous authentication provider allows users to log in to your application without providing credentials.
Step 10: Link BankingDogs Front-End to App Services Application
As noted earlier, lines 2 and 21 are the sole points of integration for your App Services application to the front end. Currently these are using the endpoints of my BankingDogs application, but go ahead and replace them with your own App Services App ID and GraphQL API endpoint. Navigate back to GraphQL in the left menu to retrieve these.
Step 11: Insert GraphQL Queries
Notice on lines 29-44 in our BankingDogs.js file that we have the query which can be simply cut and pasted directly from our GraphiQL interface.
You can see in the application we are also using a QueryInput to filter for a specific user:
Opening the console in the developer tools, will also show you the entire response payload. Go ahead and play around with your queries in GraphiQL and in the application.
Working through our BankingDogs application tutorial, we see how our secure GraphQL server exposes a single endpoint to respond with exactly the data our client application requested - nothing more, nothing less. This simplicity and flexibility leads to cleaner code, reduced payloads, optimized performance, and increased developer productivity. It's easy to see how MongoDB App Services' GraphQL support has made data even more stunningly easy to work with.
For our BankingDogs application, we have only queried data with one role. If you would like to do a more. sophisticated application and employ mutations and varied roles, check out Drew DiPalma's talk from MongoDB .live 2020: Creating Sophisticated GraphQL APIs in Minutes.