Implement Full-Text Search over a GraphQL API in Atlas
Rate this tutorial
GraphQL can be an extremely powerful and efficient way to create APIs and MongoDB Realm makes it easy by allowing you to connect your collections to GraphQL schemas without writing a single line of code. I wrote about some of the basics behind configuring MongoDB and Realm for GraphQL in
tutorial a while back.
As you find yourself needing to do more advanced things with GraphQL, you're going to need to familiarize yourself with
. If you can't map collection fields to a schema from within Realm and you need to write custom logic using a serverless function instead, this is where the custom resolvers come into play. Take the example of needing to use an aggregation pipeline within MongoDB. The complex logic that you add to your aggregation pipeline isn't something you can map. The good news is that you don't need to abandon MongoDB Realm for these scenarios, but you can leverage Realm's custom resolvers instead.
To be clear, MongoDB will instantly create a GraphQL API for basic CRUD operations without having to create a function or write any code. However, you can easily extend the functionality to do things like full-text search, which is what we're going to accomplish here.
To be successful with this tutorial, you'll need a few things:
We'll be using a MongoDB Atlas cluster for our data, a Realm Function for our custom resolver, and some basic Realm configurations for our API. All of this can be accessed from your MongoDB Cloud account.
The expectation is that your Atlas cluster already has network access rules, users, data, etc., all defined and configured so we can jump right into the GraphQL side of things. We're also expecting that you have a Realm application created, even if it isn't configured. If you need help with configurations, check out
on the subject.
At this point, you should at least have a blank slate in terms of a Realm application. To get up and running with GraphQL, we need to accomplish the following:
- Configure authentication methods.
- Define a Realm JSON Schema.
- Establish access rules for authenticated users.
It might sound like a lot of work, but our configuration is mostly point and click.
Within your Realm application, navigate to the “Authentication” tab. From this dashboard, you're going to want to enable "API Keys" and create a new API key.
The name of your API key is not too important for this example, but it could be if you're in production and need to keep track of your keys.
After making note of your API key, click the “Schema” tab from within the Realm dashboard.
If you don't already have a schema defined for the collection that you plan to use, click “Configure Collection” and then “Generate Schema” from within the “Schema” sub-tab. Generating a schema will analyze your collection and create a Realm schema for what it finds in the sample data. You can also define your own schema if you don't want it automatically generated.
It's not too important for this example, but my schema looks something like this:
The documents in my collection have three fields—a recipe name, a string-based array of ingredients, and a document id.
The final step to configure is the rules on who can access the data from your API.
Within the Realm dashboard, click the “Rules” tab.
Find the collection you would like to apply a rule to and then make the default rule read-only by selecting the checkboxes. In a production scenario that is outside of the scope of this example, you'll probably want better-defined rules.
At this point, we can work on our custom resolver.
The GraphQL API for our collection should work as of now. You can test it with GraphiQL from the “GraphQL” tab or using your GraphQL client of choice.
From the “GraphQL" tab of the Realm dashboard, click the “Custom Resolvers” sub-tab. Click the “Add a Custom Resolver” button to be brought to a configuration screen.
From this screen we're going to configure the following:
- GraphQL Field Name
- Parent Type
- Input Type
- Payload Type
The GraphQL field name is the field that you'll be using to query with. Since we are going to do an Atlas Search query, it might make sense to call it a
The parent type is how we plan to access the field. Do we plan to access it as just another field within our collection, do we plan to use it as part of a mutation for creating or updating documents, or do we plan to use it for querying? Because we want to provide a search query, it makes sense to make the parent type a
Queryas the choice.
The function is where all the magic is going to happen. Choose to create a new function and give it a name that works for you. Before we start writing our custom resolver logic, let's complete the rest of the configuration.
The input type is the type of data that we plan to send to our function from a GraphQL query. Since we plan to provide a text search query, we plan to use string data. For this, choose
Stringwhen prompted. Search queries aren't limited to strings, so you could also use numerical or temporal if needed.
Finally, we have the payload type which is the expected response from the custom resolver. We're searching for documents so it makes sense to return however many of that document type come back. This means we can choose
Existing Type (List)because we might receive more than one, and
[Recipe]for the type. In my circumstance
recipeis the name of my collection and Realm is referring to it as
Recipewithin the schema. Your existing type might differ.
We need to add some logic to the custom resolver function now.
Let's break down what's happening for this particular resolver function.
In the above code, we are getting a handle to our
recipescollection from within the
fooddatabase. This is the database and collection I chose to use for this example so yours may differ depending on what you did for the previous steps of this tutorial.
Next, we run a single-stage aggregation pipeline:
In the above code, we are doing a text search using the client-provided
querystring. Remember we defined an input type in the previous step. We're searching the
namefield for our documents and we're using a fuzzy search.
The results are transformed into an array and returned to the GraphQL client.
So how can we confirm it is working? We can test it in GraphiQL, Postman, or anything else that can make HTTP requests.
From the “GraphQL” tab of the Realm dashboard, visit the “Explore” sub-tab if it isn't already selected.
Include the following in the GraphiQL editor:
recipesquery will return all documents in the collection while the second
searchquery will return whatever is found in our function.
While we didn't use the API key when using the GraphiQL editor that was included in the Realm dashboard, you'd need to use it in your own applications. With Realm’s authentication options you can utilize Atlas Search via GraphQL in your client-side and backend applications.
You can add a lot of power to your GraphQL APIs with custom resolvers and when done with MongoDB Realm, you don't even need to deploy your own infrastructure. You can take full advantage of serverless and the entire Realm and MongoDB ecosystem.