This course has been updated! We now recommend you take the API Design in Node.js, v3 course.
Transcript from the "Resolvers Overview" Lesson
>> Scott Moss: Resolvers, once you learn this part, you can pretty much do everything with GraphQL, other than mutations. So we'll go over resolvers. We'll cut to the challenge, and then we'll eat lunch. Then we'll come back and talk about it. So resolvers, okay, it looks like I was about to put something here.
[00:00:16] [LAUGH] But I don't know what happened. Did it not save, or I lost the Internet, I don't know. But I'll just talk about resolvers, functions to resolve data for your schema. So again, let's look at this example right here. I'm just gonna walk through it. I think it's better if I just walk through it as I talk.
[00:00:33] So again, this is a resolver. I wanna talk about this argument syntax here. So resolver takes in four arguments. The first argument is gonna be the rootValue. rootValue is optional, because it's up to you to pass in the rootValue. Most of the time, you won't have anything here, on a top level resolver.
[00:00:58] On a nested resolver, when we get there, this will always be something. It'll be the thing that is being resolved before it. [LAUGH] Okay, so a nested resolver is another branch. It's rootValue will be the branch that it branched from, right? But because this is a top level resolver, as in it is a branch from the root, there's nothing above it.
[00:01:21] There is no rootValue, unless we supply it. You can supply a rootValue here, if you want. Or I'm sorry, here. You could put a rootValue here, like rootValue, and you can put whatever you want. And that rootValue will be passed down to your top level resolvers. What that's mostly gonna be used for, that I've seen, is for per request caching.
[00:01:47] So there's something called a data loader. We're not gonna get to data loader today, but it allows you to cache things per request. Cuz if you do the math and you kinda thought about this for a minute, somebody could query GraphQL API, and recursively make it query the database, and kill your API.
[00:02:03] It could kill it. They can, for instance, if your user type had a song, and your song type had a user, you can ask the user for the song, ask the song for the user, the user for the song. You can do that over, and over, and over again.
[00:02:14] And every time you have a resolver, it will create a database for that, right? It'll just go on, and on, and on, and on. So you can stop that, you can have a rootValue that caches it. So it's like I've already got this user once, cache it here.
[00:02:25] So if it asks for a get, then just get it from the cache, per request. So it's a lot of advanced stuff you can do there. But yeah, you could have a recursive GraphQL server that gets pooped on, so you've gotta be careful. So the first one's the rootValue, which is normally empty unless you pass in something, or if you're on a nested resolver.
[00:02:43] The second one is gonna be any arguments that are passed in. So when we pass in arguments and mutations, that's what this second one's gonna be. It's gonna be an object of those arguments, with the same name as they are in your GraphQL. So if we went to here, and let's say, I don't know.
[00:03:00] Let's say getMe took in an argument of hello, whose type is string, right? Inside of that resolver, we would have an object. We would call this args, there would be an args.hello on there. That’s how this works. This is an object of all the arguments for this given resolver.
[00:03:19] Normally, what you would see, especially in my code, I'll just destructure it like this. So I would just be like, hello. I'll just get it right there, destructuring using ES6. Which is the same thing as args.hello. It's the same thing as saying,
>> Scott Moss: The same thing, I'll just destructure it in there.
[00:03:40] Another pattern that I follow is, I always put arguments on a property called input. So when you see me do input, when we start writing inputs, I always use the variable name input. Which has nothing to do with the input type in GraphQL, I just name it input.
[00:03:55] That way I know inside my resolver, it's just always an input argument. The only time I wouldn't use input, if it was just one argument like an ID, I'll just put ID. But if it's like you're doing an update, cool, here's an input. So I know it's always called input.
[00:04:08] It helps you a lot when you start writing nested resolvers. Otherwise, you will have to remember every signature of every single mutation that you wrote, and that can be tedious. It's better just to give them all the same name. It's like, there's an input. So that's the second argument.
[00:04:23] The third argument is gonna be called the context. Context is, remember when we did this down here, this context object? That's what context is. It's kinda like a rootValue, except for it's more like, it's not just values. You can pass functions down here, pretty much anything that needs to be shared, for all your resolvers.
[00:04:43] So in this case, the context is the request that was coming in. So we pass the request down, we pass the user down. So that's the context. You'll use this one a lot. And then the last one, which you will probably almost never use, unless you're getting really advanced, is gonna be info.
[00:04:58] And this is gonna be the native AST for the GraphQL query that came in. If you were to log this, you would just see some crazy stuff. But it's very helpful to do optimizations on a database query. For instance, you can take this AST, you can feed it to Mongo.
[00:05:13] And for instance, if I did a query where I only asked,
>> Scott Moss: Let's go back here. So let's say I did a query run where we asked for the ID. That AST that comes in, that object that's gonna represent this query, is only gonna have id property on it.
[00:05:30] So then I can tell Mongo to only query for the id field, and not query for all the fields. And then give it to GraphQL, have GraphQL only pop off the id, cuz that's what would happen. So by saving database queries, I can use that AST to tell my database to only query for the fields that were asked for.
[00:05:46] So that's an optimization you can do. That leads to problems with caching, especially if you're doing Redis. So you've gotta figure that out. But yeah, you can do that. But you almost never use info.
>> Scott Moss: I mean, I'll log it for you, you can see what it look like.
[00:06:03] But if you wanna see what raw GraphQL looks like, I'll just log this for you here. We can run this,
>> Scott Moss: We will run our query. We'll go back, and here you go. Here's the AST for that query. Here's the field name, let's call it getMe. All the nodes, this is the raw GraphQL query associated with that.
[00:06:27] The schema, any directives, we're not gonna talk about directives, pretty much everything associated with that. So you can take this, and you can see that you can use this information to associate to your database what you actually need to query and what you don't need to query. So like I said, you'll probably almost never use this.
[00:06:46] This is for very, very advanced cases. And you probably just will never use it, but it's there. Most of the time, what you're gonna be using,
>> Scott Moss: Is gonna be args and context. You'll probably never even fill this in. And this will be just a placeholder until you get to nested resolvers, then you'll have to use this one.
[00:07:06] But most likely, it's just these two, args and context. So that's the signature for our resolver. Now, for an actual resolver, this is how you hook it up. So we're using GraphQL Tools, which allows us to hook it up in a specific way. Depending on what mutation you use, you would hook up your resolvers differently.
[00:07:24] We're using GraphQL Tools. I prefer this method, it makes so much more sense to me. So the way it works is, if we go look at the GraphQL file, we have a type called Query here, right, on this user.
>> Scott Moss: Guess what that's doing.
>> Speaker 2: Queries the user?
>> Scott Moss: Well, getMe queries the user, but what do you think this is doing? We know what this is doing, right? type User, that creates a user type. What is this doing?
>> Group: Creating a query type.
>> Scott Moss: Yeah, it's creating a query type. Yeah, it creates a query type, and that query type gets associated and,
>> Scott Moss: Here, there's our query target. It gives a certain shape to this query here. I just put it in the user file. You only make the query type one time, and the mutation type one time. So we made a query type. And then inside that query type, we made a query called getMe.
[00:08:14] I'm gonna get rid of these args, cuz we don't need this args, right? So we made another one called getMe. Notice the name, getMe, camel case, right? I'm gonna pull up the resolvers.
>> Scott Moss: Look at that, it is also called getMe, inside here, the function name. These have to match.
[00:08:33] If they don't match, it will break. And in fact, everything has to match. So I have userResolvers, I defined for the Query, with a capital Q. Which is exactly the same over here, Query, that I want to make a query called getMe. And I don't have to put it twice, cuz it's ES6, right?
[00:08:51] It just looks it up.
>> Scott Moss: And it's called getMe. If I change the name of this, this will break. If I say, getMeee: getMe. Now I'm gonna get a error, most likely right here. It says it right here. Cannot apply update, query.getMeee defined in resolvers, but is not in your schema.
[00:09:13] You need to restart your application, right? That's because they're saying hey, you defined a resolver called getMeee with three e's. But inside your schema, you don't have that. So that's why it's freaking out, so they have to match. You can turn that error off if you want, but I do not recommend it.
[00:09:31] So if we go back to our resolver and we replace it, we go back, then it matches. So those have to match. Any questions on that? So we have our query here. If we had more queries, for instance, if we had getAll users, then we'd have to make a function.
[00:09:48] I mean, then we'd have to go inside of here and do getAll. Now we're gonna make another one called getAll, and that returns an array of users.
>> Scott Moss: And then now we have to make getAll over here. The same would happen if I don't, if I put it in a schema and don't put in a resolver, I'll also get an error.
>> Scott Moss: Or maybe not. Maybe it doesn't give me an error unless I run it. I've gotta refresh GraphiQL. Yeah, so sometimes you've gotta refresh GraphiQL, cuz the introspection query is old. So make sure you do that. There’s getAll. And then I’m, like, cool, give me the id. Then it’s like, null.
[00:10:29] You try to do,
>> Scott Moss: A user that’s non-null, but we don’t even know how to resolve this. You didn’t tell us how to resolve it, so it’s just null. So yeah, you're gonna get an error either way, if you don’t match this stuff up. So make sure they’re matched up, that's the first rule.
>> Scott Moss: And we do have them matched up here.