This course has been updated! We now recommend you take the API Design in Node.js, v4 course.
Transcript from the "Solution: Resolvers" Lesson
[00:00:03]
>> Scott Moss: I'm gonna walk you through it and you're totally gonna have a better understanding after this. So what you were tasked to do, you were suppose to create resolvers for get and get all on both the song and playlist models. And then expose them to the GraphQL router, correct?
[00:00:24] All right let's do that. So let's go to, we'll start with the songs first, so first you would probably have to create a song dot resolvers file so if you didn't start with that, you could have looked at the users resolvers file to get a clue of how to do that.
[00:00:38] And then the next thing is, we know we need to get and then get all, so what I'm gonna do is open up the song.graphQL here.
>> Scott Moss: So I got the GraphQL file on the right, the resolvers on the left, and we can kinda walk through what's going on.
[00:00:56] So let's work on get song. So if we were to get a song, let's just delete this and walk through it. The first thing is this is a top level resolver so the root value's probably nothing. So I'm just gonna put a place holder. That's just me putting a place holder.
[00:01:10] That's all that is is just a place holder that I'm never gonna use. This is the argument circuit we passed in. If I'm gonna get one song, most likely I'm gonna need to supply an ID. To get that one song most likely. How else would I be able to get one song?
[00:01:25] We did the same thing on REST. For REST it was /api/song/idparameter, remember? This is the same thing, except for it's not passing a URL. It's passing as an input, an argument. So that means I need to get an ID here. So if I were to come down here, when I make this query, you can see that there's a ID, it's right there.
[00:01:46]
>> Scott Moss: That's the ID. That ID, is this ID.
>> Scott Moss: It's type, this is caller type of ID, making it required. It returns a Song type that is also nano, which is up here. Everybody following me so far? Okay. So, we got the ID. This user, let's just assume it's gonna be passed down, we don't really need it in this query.
[00:02:09] In fact, let I'm going to get rid of it to reduce the complexity. But that user is always there on the context object because whenever we pass that down on the GraphQL router here, that's where that user's coming from. We also have their request object. That's always gonna be in the context, whether we need it or not.
[00:02:26] And the production app, we would probably use that to make sure that the user has the authentication rights or the role to access this resolver. Maybe search for songs specifically for this user. So you would use that a lot. But for now we're not doing that. So knowing that, we just need to do a, we have a ID, we have the song model.
[00:02:45] We need to return one song. If we go look at the controllers that we had, the queries in the query file that we made, didn't we have something here that finds by an ID? Right there, findById. So we can use the findById method here on the song model to find a song by a given ID.
[00:03:06] So we can say Song.findById(id) and now we have it. So let's get one song. Who got this? Okay. Cool. So let's get one song and then get all songs. So let's get rid of that. Let's start down here on the query. So we'll make one, we'll call it all songs.
[00:03:30] You can call this whatever you want. This is just the way I write my GraphQL. I've written so many schema and this is just like how I started to write them. I named them all the same, allsongs, all, followed by the type pluralized. If I getting one, I just capitalize the type.
[00:03:47] I'm just gonna look at one of these types. You can name it whatever you want, doesn't matter, as long as it matches with your queries and your resolvers. They just have to match, however you wanna do it. So for allsongs, it doesn't take any arguments. And the production app maybe it would, it would probably take in like, what you wanna filter by.
[00:04:03] And you might have some filter inputs here like I want songs whose name start with whatever. Filtering here. But for now, we don't have any arguments. It's just gonna return an array. Of songs. That array has to be non-null. So it should always be an array, even if the array is empty.
[00:04:19] If I wanted to require that it always has to be a song in there, I can put an exclamation there and it says it has to be an array that also always has a song in it. But that's not the case. You might not have songs. But I should still get back an array.
[00:04:31] So let's satisfy that. If we have all songs, we don't take any arguments, we don't need the user here, then I'm not really gonna use any of these arguments up here. I don't even really need anything. I'm just gonna return every song in the database which is song.find with an empty query and execute it, that's gonna get all the songs.
[00:04:53]
>> Scott Moss: All right, so we'll save that. And then down below this is just matching. So if you pull these up side by side, for the query type we're gonna make a new field called allsongs whose value is allsongs. That's what happened here, for the query type we made a new field called allsongs who's value is gonna be the result of this function.
[00:05:15] Which Song.fine does turn return an array. Song.fine along as it doesn't error out on the database will always return an array. Whether it's empty or not, that's just how mongoose works. Always returns an array whether it's empty or not unless it errors out because It broke so this function will satisfy this type we're good there.
[00:05:34] Now for the song one we say on the query object we made a new field called Song with a capital S whose value is gonna be the result of the getSong function. So, Song right here on the query type. Whose value needs to be the song type. Don't confuse the two.
[00:05:54] This Song function right here has nothing to do with the song type. That's just the way I named it. If that was confusing you, then I can call this like anything [LAUGH] I can call this anything. As long as it's also called anything over here
>> Scott Moss: Does that make sense?
[00:06:19] So, those two have nothing to do with each other. That's just my convention for naming queries that get one type, all right? I just normally call it by the same type name. So, if we look at this, getSong. Find by ID either returns the song or it's gonna return null.
[00:06:37] So, does this really satisfy our query here?
>> Scott Moss: It's not a trick question. Does it, does it? Cuz think about-
>> Speaker 2: It can't return null.
>> Scott Moss: It can't return null, exactly. If this does not find a song in the database with this ID its just gonna return null.
[00:06:59] Then this will throw an error, when I say this GraphQL will throw an error like hey it's null. You got two choices here, you can either get rid of the null restriction, which I don't think I recommend. Or what you would do here is you would throw the error yourself.
[00:07:13] If it's null, which is probably a better approach. So what I would do is come in here and I would say,
>> Scott Moss: [INAUDIBLE] so I would say Song.findById and then if it was not nosong, then I would just throw
>> Scott Moss: Cannot find song with ID. If it is there, I'll just return song.
[00:07:49] Now I'm just gonna throw an error in. That might seem like whoa, where's that error going? Are you gonna break the app with that. Well we're using Apollo server, and what they do is, any error that's thrown in the resolver is automatically be caught for you. And it's going to be just given to the client.
[00:08:06] So that can be good or bad. If you put some systemic information in the error, and the client sees that, you probably don't want that, right? There's a better approach to handling errors. I have a purse that I use on my back end. It is a little more complicated.
[00:08:19] So basically the crux of it is that you create sophisticated error messages and error codes that your front end can respond to. And then your front end just responds appropriately. Just do not send sensitive information, that's basically it, but end of the day, you are just throwing errors and then figuring out what message to throw.
[00:08:37] First, let's sort of express where you pass an error to next, and then at the end of your express handler you forgot what message to send back, and a status code. There is no status codes here. It's mostly based around whatever convention you create for your errors. For me, what we do is, when we throw errors, we give them like a code.
[00:08:55] So it might be error code 1-10. And then the client knows what to do if the code is 1, and knows what to do if the code is 2 and knows what to do if the code is 3, and it responds appropriately. Does that makes sense? So this will satisfy, so this will always return a song or it will error before here.
[00:09:13] But GraphQL should never say there's no song. It should never say that. It will say there is no song here though but GraphQL would never complain about any questions on that? Okay, so let's go to Playlist,
>> Scott Moss: And you can go over here. This was probably harder, right?
[00:09:43] Because it has a song type, so if you go play this resolver, we need to do a get play list, actually let's look at this so let's first go here. So first we need to do a get and a get all. So let's define the get, and that's gonna be the playlist.
[00:10:04] Again, this is just my convention. You can call it whatever you want. Whenever I'm going a git on a type, I just normally give the query the same name as the type itself. That's just my convention. So I'm also probably gonna need a ID to get a playlist by the specific ID.
[00:10:20] So I'm doing the same thing. we can make the same optimization we just made, where we're throwing an error if it's null. So we can also do that here as well. And then for all playlists, it's exactly the same thing. I'm just going to find and exec and that's going to do that.
[00:10:37] Now, can anybody tell me why this wouldn't work?
>> Scott Moss: It's exactly the same as what we just did. But because the playlist type in GraphQL is slightly different, why would this not work? Or even better, can you tell me where this would fill? What query would someone have to ask for in order for this to break?
[00:11:02]
>> Speaker 2: Are you talking about the song?
>> Scott Moss: Talking about the song, yeah, the songs. We didn't tell GraphQL how to resolve songs. Right now the way this is stored in the database if you look at the playlist model, right, it's stored in the database as an array of strings So, when we query, let's say I get one play list, and then I also get the songs, right.
[00:11:24] I think the songs pulls to be current graph qo, the songs are song tight. We know that song tight was an object, right. But in data it is strange. So what I tried to query it, GraphQL goes like I got a string, for which it has to be a song.
[00:11:40] We didn't tell GraphQL how to resolve the songs inside that model. So this will break. And I can demonstrate it. So let's just save all of this.
>> Scott Moss: Start this up and, something.
>> Scott Moss: Did I do here something?
>> Scott Moss: What happened? Did y'all see me delete that?
>> Scott Moss: I didn't delete that.
[00:12:09] Okay, so let's go back over here. We'll refresh and then we'll say,
>> Scott Moss: Let's just get all songs, right? So get playlist, get all playlist. All right, this is allPlaylists, there we go. We don't have any playlists right now, so obviously we hit empty array. So, we would have to create a playlist.
[00:12:30] I'm just gonna create a playlist right quick, so we can see what would happen. Actually, maybe it'll throw the error if I just do it now. No it won't, okay, I'm just gonna create a playlist so we can all see what I'm talking about here. So let me just come down here.
[00:12:45] So let's say Playlist.create, this title.
>> Scott Moss: Hello, we'll actually have to create some songs too. That's too much work. All right. When we get to mutations I'll show you that's too much work. But basically, see look it's already happening right here. I'm on the allPlaylist and I'm saying, cool.
[00:13:17] I want the songs and I wanna get the titles of those songs, right? But, in the database, it's just an array of strings.
>> Scott Moss: These songs are just an array of strings. We haven't told GraphQL how to convert those, it's not even GraphQL. We haven't even told mongo how to convert those array strings to an object.
[00:13:40] Cuz GraphQL is just gonna take what it gets. This right here, it's just going to return a playlist that has a title and array of strings for songs, and a favorite. And they we're saying I want for each item in that array, I wanna get the ID and the name, no, we can't do it.
[00:13:53] There's no such things. It's just not there. So in order to do that, you have to do a resolver or you can greedy and be naive and just populate it all right here. Like what I said last time, it's very, very wasteful. Cuz you might, you will just be doing it it in case someone asks for songs.
[00:14:15] Well they never ask for songs you still populated anyway which is expensive. So you probably don't wanna you would have to make another resolver down here. And we're really gonna get to that, you all in the last lesson but there is a lesson I'll go to more detail about that
[00:14:32]
>> Scott Moss: So if you got this far, then, yep, that's what you had to do. And then also you just had to make sure that the resolvers were inside of the GraphQL router file, which consists of just importing the resolvers here. Adding them to this merge, but not before exporting them and their appropriate local GraphQL router file.
[00:14:53] So every resource has their own GraphQL router file, where the type is being exported. You will also have to export the resolvers there. So the song.GraphQL.Router needed to export the resolvers, and then playlist.GraphQL.Router will also need to export its resolvers. Therefore it could have been imported in the main GraphQL router up here.
[00:15:12] And then if all these was too confusing, you could look at the user example, it's doing the same thing minus the differences between the queries.