How to Use GraphQL to Query a MongoDB Database

In this lesson, we'll look to have our GraphQL API resolvers interact and manipulate data in our Mongo database. This will help ensure persistence of data and data changes between server restarts.

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL with a single-time purchase.

Thumbnail for the \newline course TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL
  • [00:00 - 00:17] In this lesson, we'll modify our GraphQL resolvers to now manipulate and use information from our database. We'll start off with a simple one by modifying the listings query first, which currently just returns the mock listings array we've set up in our code.

    [00:18 - 00:31] We're going to need to access the database object in our source index file to be able to interact with the database. Then in our Apollo server instantiation, we've placed the database object in our context.

    [00:32 - 00:41] So now we should be able to access the database from all of our GraphQL resol vers. Context is the third positional argument of a resolver function.

    [00:42 - 00:57] The first argument is the object, but in this case is the root object passed into this field, which is undefined, and as a result, we'll give it the undefined type. The listings query field here doesn't accept any arguments, and arguments are always an object.

    [00:58 - 01:14] So as a result, we'll specify the type of this arguments parameter as an empty object literal, since it contains no properties. Where pre will prefix both the root object and the arguments parameter with underscore since we're not going to use it in this resolver.

    [01:15 - 01:22] Finally, the context arguments contains the database property we want. So here we'll destruct the database property.

    [01:23 - 01:44] The type of this DB property is the database interface we've already set up in the source lib types file. So we'll import the database interface and assign it to the type of this database object we're going to use.

    [01:45 - 02:01] Our resolver functionality is going to be very simple, and it's going to use the Mongo find method in our listings collection to simply return all the documents in the collection. To ensure we receive an array back, we'll apply the to array method.

    [02:02 - 02:22] Now note that this find method allows us to find documents based on a query, but when I really specify a query here, so it simply returns everything in the collection. To ensure that we receive the result of our find method, we'll use await and make our listings resolver an asynchronous function.

    [02:23 - 02:37] Now we'll modify the delete listing mutation to delete a listing from our database. We'll access the DB object from the context like we've done before, and then we 'll also remove the functionality we've had in this resolver.

    [02:38 - 02:58] In this instance, we'll use a method that Mongo gives us called find one and delete, which allows us to delete a single document based on filter criteria. Here we have the ID as a parameter, and we can apply a filter to delete a document based on the underscore ID field in the document.

    [02:59 - 03:18] Note that the underscore ID here we have to specify is the object ID of the document, not the string version being passed in. Just like how the object ID function allows us to create a new object ID, it also accepts a string value to convert it to the valid hexadecimal format.

    [03:19 - 03:38] So we'll apply that in and import the object ID class at the top of our file. The find one and delete method returns an object with which we'll just simply call in this particular case, delete results.

    [03:39 - 03:59] And then the value of this object within the value property itself actually contains the value of the deleted item if the actual function was successful. So in this particular case, we're going to say if for some reason or another, the delete result dot value property doesn't really exist.

    [04:00 - 04:21] So null or undefined, we're simply going to throw an error that says that the deletion wasn't really successful. Otherwise if it was successful, we're simply going to return the value of the deleted item, which in this particular case will be the item that actually has been deleted from the collection.

    [04:22 - 04:37] Since we're using a weight to ensure that we want to finish for the results of this, we'll make the delete listing resolver function asynchronous as well. With our resolvers updated, we no longer need the mock listings array we've set up before.

    [04:38 - 05:03] So we'll remove the import that we have here, and we'll also delete the listings file that was responsible in setting up this mock data array within our source folder. Okay, if we started our server and tried to run either the listings query or the delete listing mutation, they would actually both fail.

    [05:04 - 05:17] This is primarily due to how our listing object type is set up in our GraphQL schema and how our listing document is being returned from the database. Now first of all, there's something important we should talk about.

    [05:18 - 05:37] If we recall, we've mentioned that practically every field in a GraphQL schema needs to have a resolver function responsible in resolving that field. When we take a look at our GraphQL schema, we know that now we have resolver functions for both the query and mutation root object types.

    [05:38 - 05:43] But what about the listing object type? How are these particular fields actually being resolved?

    [05:44 - 05:55] We haven't done anything here. To continue to follow the appropriate standard in our resolvers file, we should be setting up a resolver's map for the listing object type.

    [05:56 - 06:05] Let's go ahead and try it out. We'll specify a new listing field, which refers to the listing object type, and we can specify the resolvers for each of these fields.

    [06:06 - 06:19] Let's assume we wanted to resolve the title field. In the root level listings and delete listing field, the object arguments for each of these fields were undefined.

    [06:20 - 06:28] However, the fields within the listing object type, the object arguments will be defined. What is it going to be defined as?

    [06:29 - 06:42] It's going to be defined as whatever the root level fields specify in return. When we take a look at our schema, we know the return type of the listings field and delete listing mutation will be the listing object type.

    [06:43 - 06:50] When we take a look at the resolver functions, we see that these functions return values. Where do these values go?

    [06:51 - 07:04] These values become the object argument for the children fields. The object argument here in the listing object type is going to be the listing object returned from the parent fields.

    [07:05 - 07:25] This listing object is essentially the shape of the listing document in our database collection. We have a type for this in our types file called listing, so we'll import that type and assign it as the type of the object argument.

    [07:26 - 07:48] And since we know this object is a listing, we'll name this object parameter accordingly and call it listing. Here is where we can specify how the listing object type in our GraphQL schema is expected to be resolved and as a result, specify the returned values from our root level fields.

    [07:49 - 07:56] Think of it as a chain of functions. When we query listings at the top level, it calls the listings resolver function.

    [07:57 - 08:12] The listings field return type is the listing object type, so it follows along and calls the resolvers within the listing object type. Okay, we know the listing object has a title which is of type string.

    [08:13 - 08:26] So we can simply have the resolve function here return a one to one map, just return the title directly. For the majority of fields in our listing object type, we can do the same.

    [08:27 - 08:38] We can specify the return value being directly from the listing object. So image, for example, will just simply be returned as listing dot image.

    [08:39 - 09:08] These resolvers are known as trivial resolvers, since they simply return a value from the past in object using the same key specified in the object type, title, title, image, image. Any graph QL libraries, Apollo server included, allow us to omit the simple res olvers, since if a resolver isn't specified, it would simply read for the property of the object of the same name.

    [09:09 - 09:23] So it essentially becomes taken care of for us. Now, remember, even if we don't specify trivial resolvers, these fields are being resolved but being taken care of by the graph QL library.

    [09:24 - 09:39] There's one field we'll have to explicitly resolve in our listings object type. A listing document contains an underscore ID field, while our app API expects an ID field within the schema.

    [09:40 - 09:56] Since the listing object being passed from the root field doesn't contain an ID field without an underscore, we can't expect a trivial resolver to occur here on its own. You'll need to be explicit and define it.

    [09:57 - 10:09] We can say the ID field will simply be equal to the underscore ID field from the listing object. Now, this underscore ID field isn't really a string, it's of type object ID.

    [10:10 - 10:22] To ensure compatibility, we'll apply the two string function JavaScript provides to convert this object ID to a string. Why do we want to make ID here a string?

    [10:23 - 10:41] In our schema, we said the type of the ID field in our listing object type is of GraphQL ID, which gets serialized as a string. In our ES lint setup, we've customized one of the rules we had to say that we don't always have to specify the return type of functions.

    [10:42 - 11:00] In this case, it might have been helpful to actually do so, because you might have already noticed when we were returning values from our resolvers that don't actually match the type we expected to, we weren't getting any type script warnings or issues. In this case, we can see for this ID field, we're returning an object ID, but we wanted to return a string.

    [11:01 - 11:14] So we'll be explicit and say that this function should return a string and it will be warned if we try to do something that didn't match that. So let's bring back the two string function here.

    [11:15 - 11:23] And then we'll do something similar for the two resolvers above this delete listing resolver function should return a listing. But we made this asynchronous.

    [11:24 - 11:39] So what we'll say here is it will return a promise that when resolved would be the listing object and something similar will be done for the listings field in our query root object. We're returning now a list of listings or an array of listings.

    [11:40 - 12:00] So similarly, it will return a promise because it's an asynchronous function and only when resolved would have returned an array of listings. Now how you try to sort of type define the arguments and the function returns in your resolvers map is totally up to you.

    [12:01 - 12:12] There's many different examples of people extrapolating some of this information to an interface and then extending and using that interface above for the resolvers object. But in this case, we try to follow this particular format.

    [12:13 - 12:32] We use the I resolvers interface that our polar server express gives us to sort of confine the fact that we want our resolvers map to be a list of object types that contains fields or just direct fields themselves. And then in our fields, we explicitly define the types of our arguments and then we specify the return type of each of these functions.

    [12:33 - 12:43] Now let's verify that everything works just the way we want it to. We'll head to the terminal, run our application.

    [12:44 - 12:48] And when running, we'll head to our browser. We have our atlas UI open here.

    [12:49 - 12:57] But the first thing we're going to go to right now is our actual GraphQL playground. And we're going to attempt to actually query our listings.

    [12:58 - 13:12] At this moment, the only two fields we had that we actually want to query in our list is the ID and title fields. We press play and we get the ID and title for the three listing objects in our actual database.

    [13:13 - 13:19] Notice the ID fields. They're the string representations of the actual ID fields from our collection.

    [13:20 - 13:24] Amazing. We're actually querying directly from our database.

    [13:25 - 13:32] Let's run the delete listing mutation. So we'll specify delete listing, we'll now try to give it an ID value.

    [13:33 - 13:40] Let's just copy an ID that we can retrieve from the actual list that was returned from our query. We'll take the very first one here.

    [13:41 - 13:49] And in this case, let's just see if we want to return the ID and title of this delete listing. We press play and then we get this information back.

    [13:50 - 13:56] That was pretty quick as well. This tells us most likely that our actual delete and listing mutation works as expected.

    [13:57 - 14:13] If we go to our Atlas UI, refresh this particular collection and database, we 'll now only see two results being found or in other words, two documents in our collection. That's because we've just deleted that particular document that we specified from our collection.

    [14:14 - 14:23] If we actually go back to our playground and attempt to query all the listings we have, we only now get the two documents available in our collection. Amazing.

    [14:24 - 14:31] We've practically fully set up a simple GraphQL API that now interacts and uses a Mongo database.