Building a GraphQL Resolver to Receive New Listing Data
We'll continue from what we've done in the previous lesson by having the `hostListing` resolver function we've created receive an input with new listing information which will then be added to the `"listings"` collection in our database.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo 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 - Part Two course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two with a single-time purchase.
[00:00 - 03:24] In this lesson, let's look to have the host listing resolver function receive an input with listing information and we'll add a new listing document from that information to the listings collection in our database. It's probably important to first discuss what kind of information is going to be provided from the client to the server to create a listing. When we are going to build the form for where the user is going to create a new listing, from top to bottom, the user will provide information such as the listing type, whether it's an apartment or house, the maximum number of guests, the title of the listing, the description of the listing, the address, city or town, state or province, and zip or postal code, the image of the listing, and the price of the listing per day. So essentially, the input we're going to have the client provide to the server will be the following fields we've just mentioned. There are other fields in our listing documents in the database that govern the context of what a listing document is to have in an app, such as the number of bookings in a listing, the idea of the host of the listing. For these fields, we won't need the client to provide the information since we'll govern the values ourselves. So, for example, when the listing is first created, the bookings list will be empty . The host ID will be the idea of the viewer making the request. The fields we're going to capture from the client for the most part are going to be mapped one to one. So for example, whatever the user says the title of the listing is going to be, will simply make it the title of the listing document we add in the database. And this is the same for the description, image, type, price, and number of guests. Things will be a little different for the address information we're going to capture. We're not simply going to take the listing location in our client form and place these values directly in our database. And this is for a few different reasons. One, our database or our listings collection doesn't accept information such as the postal code or shouldn't accept information such as the postal code for listing documents. The location information we have for listings are the address, the country, the admin, and the city. Now, two more importantly, we'll want the country admin and city information within every listing document to be the geocoded information that Google's geocoder API gives us. We won't want to store the city or country information the user types directly in the form into our database for a multitude of reasons, one being the fact that users oftentimes may provide information differently. They may misspell something, they may type the two characters shorthand for state of province instead of typing the whole word , etc. And if we had different variations of the same representation of a location, this would affect how we search for listings in the listings page. Our Google Geocoder API requires a single input and outputs valid geographic information for this input, regardless of how terse or verbose it is. It can be just the address and the address number, or it can be the address with the postal code, the city, or the country.
[03:25 - 06:00] With that said, though the user is to provide an address, city, state, and postal code information, what we'll do is simply concatenate all this information to a single address and for it to be within the inputs of our host listing mutation. So for example, if the user was to say in the client form, the address is 251 North Bristol Avenue, the city is Los Angeles, the state is California, the zip code is 90210, and the client will parse this information and simply pass a concatenated address that has all this information together into the server. The mutation will accept a dis-address in the inputs, run it through our geocoder, retrieve the country admin and city information, and have that be kept in the new listing document. And the entire address compiled will be the value of the address field in our listing document. Okay, so now with our game plan sort of established, let's begin to build out our host listing mutation. In our types definitions file, we'll state that the host listing mutation is to receive an input of object type host listing input. And when this mutation resolves successfully, we'll want it to return the new listing document to the client. So it will return a listing object type. We'll describe the shape of the host listing input object type, and we'll note the fields we've mentioned that the client will pass on to our server. The input will contain a title of type string, a description of type string, an image of type string, we'll discuss how we're going to handle image uploads shortly, but it will be received as a string, the type with which will be the shape listing type, which is an enum we've already defined, the address of type string, the price, which is to be an integer, and the number of guests, which is to be an integer as well. We'll then create the TypeScript type for this host listing input argument in the types file kept within the listing resolver's folder. We'll export an interface called host listing args, which is to have an input of type host listing input. And the shape of the host listing input interface will be as we just described.
[06:01 - 06:48] We'll import the listing type interface from the lib types file, which we'll use to describe the shape of the type field. And the price and number of guests will be of Type Script type number. In the listing resolver's map file, we'll import the host listing args interface from the adjacent types file, and we'll declare that the host listing resolver function will receive an input argument.
[06:49 - 07:14] We'll also look to destruct the database and request objects available as context. And when resolved, we'll return a promise of type listing, with which we already have the listing interface imported.
[07:15 - 07:28] There's a few things we'll want to do in this resolver function. The first thing we'll want to do is verify that the input provided by the user is valid.
[07:29 - 08:14] And by valid, we essentially mean we want to verify that the title and description don't exceed a certain number of characters. The type selected is either an apartment or a house, and the price is in less than zero. We're going to provide some of these restrictions on the client side as well, but we're going to add some server side validations as an extra precaution. So we'll have these validations handled in a separate function that we'll call verify host listing input, and we'll pass in the input argument. We'll construct this verify host listing input function above, that is to receive the input. We'll import the host listing input interface from the adjacent types file to describe the shape of the input.
[08:15 - 08:25] For the input argument in this function, we'll destruct the fields that we'll want to validate. The title, description, type, and price.
[08:26 - 08:53] We'll say if the length of the title is graded in 100 characters, we'll throw an error that says listing title must be under 100 characters. If the length of the description is graded in 5000 characters, we'll throw an error that says listing description must be under 5000 characters.
[08:54 - 09:24] If the type of listings neither an apartment or a house, we'll throw an error that says listing type must be either an apartment or house. And we'll import the listing type TypeScript enum from the lib types file for it to be used here.
[09:25 - 10:45] If the price of the listing is less than zero, we'll throw an error that says price must be greater than zero. This is by all means not a very verbose list of validations. We're not taking the fact that we need to really validate every single listing to be provided into our app because it's very subjective and depends on the kind of app that wants to be built. With that being said, you're more to welcome to either ignore adding these validations or whether you want to introduce other validations you might think are important. In our host listing resolver function, if the input is valid and no errors are thrown, we'll then look to validate that a viewer who's logged into our application is making the listing, since we only want listings to be created for users who have already logged into our app. We have an authorized function that 's already imported in this file and used for this purpose. So we'll use the authorized function, pass in the database and request objects, and attempt to retrieve a user or viewer based on the CSRF token available in the request. If the viewer doesn't exist, we'll throw an error saying viewer cannot be found.
[10:46 - 11:53] If the viewer object exists, we can now look to get the country admin and city information from our geocoder based on the address in the input object. We'll run the geocode function from the Google object that's been imported in this file from before, we'll pass the input address and destruct the country admin and city fields. If any of these location fields can't be found from the geocoder, we'll throw an error that says invalid address inputs. Remember how we mentioned earlier when we conduct searches for a location for listings, we don't have to have all these three fields returned from the ge ocoder and at a minimum, we should have the country be found. That is valid, but if we're adding a listing document to our listings collection, we'll want to ensure that all these three fields exist for the listing.
[11:54 - 13:06] Country admin and city. Also, the address from the inputs should have enough information such as the postal code street number city state, such that the geocoder should be able to derive geocoding information. At this moment, if there's no errors, we can prepare to insert a new listing document to the listings collection in our database. We'll use the insert one method of the node Mongo driver to insert a new document to the listings collection. The underscore ID field will be a new object ID we'll create with the help of the object ID constructor from the node Mongo driver. We'll use the spread operator to add the fields from the input object directly into the document and we'll specify some other fields as well. For example, bookings will be an empty array. Bookings index will be an empty object. We'll pass the country admin and city fields from our geocoding results. And the host field would be the ID of the viewer that's created the listing.
[13:07 - 13:49] There's one other thing we'll look to do. Every user document in the user's collection is to have a listings field that contains an array of listing IDs to represent the listings that user has. So we'll look to update the user document of the viewer making the request to state that the ID of this new listing will be part of the listings array of that user document . We'll first look to access the inserted listing document. There's a few ways one can do this, but we can achieve this by simply accessing the first item in the dot ops array available in the insert results.
[13:50 - 15:13] We'll assign this value to a new constant called inserted listing and we'll be sure to describe its shape as the listing interface. Then we'll run the update one method from the node Mongo driver to help update a document in the user's collection. We'll find the correct document by the ID field being that of the viewer ID and we'll simply push the ID of the inserted listing to the listings field in this document. And finally, we'll have this resolver function simply return the newly inserted listing document. And we're pretty much done. Our host listing mutation will receive an input object that contains information about this new listing. We verify if it valid ates what we want. If so, we check that a viewer is making the request. We then check that we're able to get country admin city information from the address that was provided. If all the successful, we insert this new document into the listings collection. And we also update the user document making the request to basically specify that we have a new listing ID in the listings field. Amazing.
[15:14 - 15:24] The next lesson will now look to actually build the client functionality to have this form, which will receive the user input and finally trigger this mutation.