Server-Side GraphQL in Node.js Resolvers Q&A
Transcript from the "Resolvers Q&A" Lesson
>> Scott Moss: So I talked about doing the resolvers on the query level like this, but then like I slightly mentioned, is that you can also resolve fields, right? So if you want to resolve fields, that's where this pet object is, and this user object is, right? So remember, query is just the type, and we have a field called pets.
[00:00:21] Well, pet is a type and user is a type. And all the types can have resolvers. So just like query is a type and it resolves its fields, well, so could pet which is also a type, and it can resolve its fields. So there's nothing stopping you from resolving all the fields for a specific type with individual resolvers.
[00:00:43] By default, GraphQL will look at the return object from a top level resolver. And it will match the keys, and that object with the fields on that object's type, one for one. So by default, GraphQL actually does create a resolver for every single field. It's actually is an internal thing called the default resolver, and all it does is it looks for a key with the same name and it resolves that field.
[00:01:08] That's what it does by default. So every field does have its own resolver, whether you wrote it or not. That's how you're able to return, for instance, an entire pet object here, but only asked for an ID, and that's all you get back. And if you could look at the network tab and you would inspect that, you would only get back what you ask for, even though the resolver returned the entire object.
[00:01:27] And that's because you didn't ask for it, so they didn't get resolved. Yes.
>> Speaker 2: So when you manually define a field resolver, does that extend the default resolvers? Or does it completely replace them?
>> Scott Moss: That's a good question. Let's just walk through it so that you can see what I'm talking about.
[00:01:49] That's a very good question. So if I return an array of, I'm just gonna put an array of empty objects, there's nothing in them, they are just empty objects, okay? And then let's run this, let's see what happens. So I've put an array of empty objects. Obviously, if I try to run this, it's going to say pets must have an ID, so it breaks.
[00:02:10] Okay, cool. So then what I'm gonna do, is I'm gonna put an ID on both of these. I'm gonna say id 1, and then id 2, okay?
>> Scott Moss: And now if I run this, I'll get back pets, I didn't want id 2, right? It makes sense. But if I go down here to pet and comment that, don't worry about the image thing, and I say id for pet is going to be this.
[00:02:37] Now, what I can do is I can return whatever I want. I'm going to return 3. Let's save that.
>> Scott Moss: Yeah, sorry it can't be that, maybe 2. Something like that was a safe pet, yeah, the image thing. I'll just do that, there we go. Cool, so now if I do that and I come back, I return this.
[00:03:06] They both say 3 now. That's because of the resolver of ID that belongs to pet completely overrides whatever the parent resolver returned. Yes?
>> Speaker 2: Right, but did the default resolvers for each of the other fields still live there?
>> Scott Moss: Yes, they still live there. So, for instance, what's another thing on here?
[00:03:27] A name.
>> Scott Moss: Moose, and this one is Garfield, something like that. So I'm not, I don't have resolvers for names, so their default resolver. So they should still resolve to whatever they were, right. So, and that's because like I said, each individual field does have its own default resolver.
[00:03:56] Everything resolves down to a scalar at some point, there's no way to get around that. Even if you have a type that references a type that references a type that references a type, eventually there's a scalar there somewhere. So everything has to resolve to that and each one of those could have a resolver function.
>> Scott Moss: Yes.
>> Speaker 2: On the pets query resolver, the first argument is the context of the previously resolved resolver? Is that, in this case, pet, or is it still undefined? Because it's a top level of the query.
>> Scott Moss: So are you saying the first argument for this ID on here?
>> Speaker 2: The first underscore you got in that.
>> Scott Moss: This one?
>> Speaker 2: That's, yeah.
>> Scott Moss: That's still undefined because pets is still a top level query. But because id is no longer a top level query, because id runs after pets run. Therefore, this first argument here, guess what it'll be?
[00:04:49] It'll be the pet. It'll be one of these pets here.
>> Scott Moss: All right, so just to show you if I were to say. All right, we can just do something like this, yeah i'll just log in that's so much better.
>> Scott Moss: Log that pet and you can see, I run that, go back to this.
[00:05:17] You can see there we go, that's the pet. So that's what I meant by having these top level resolvers and you know something's a top level resolver because it exists on the query or mutation. That's how you know it's top level. Anything other than that is not a top level and they're gonna resolve after something on a top level resolves or after some field before it resolves.
[00:05:38] When we get into relationships, a pet might reference a user, the user might reference a pet. And that can go on for infinity. So, that first argument, it could be either one of those things, and depends on how the person is resolving it. So it can get confusing, but the thing is, you just don't have to think about those use cases.
[00:05:57] I think a lot of people that do GraphQL, they try to think about all the different ways that someone can query the API so they try to account for that, but that's the whole purpose. You don't have to account for it, you don't need to think about it, you just need to make the types and make the connections and then make the resolvers.
[00:06:13] That's it, as long as every field in your schema is accounted for, either by a default resolver or by one that you created. then you're good. You don't have to do anything else. As long as you know, all the fields are accounted for through some mechanism of resolving.
[00:06:29] They can do whatever they want. That's it. It's up to you to define the relationships. It's up to you to define what top level queries they can execute. Any other questions?
>> Can you show all your passing that the login context in a nd a gain in the server?
>> For sure. Yeah, so. Should just be like this. So context, which is a field that exists on the object that's passed into Apollo server should be a function. And whatever you return here is going to be on the context. Like I said, if this is not, if you're, if you go into your resolver and context is undefined for you or something like that.
[00:07:05] What I found out was in db, db where is the index, index db area. This file adapter here that syncs with the JSON file? I think this is silently failing for some individuals trying to read this from the file. And it just fails. So this file never gets exported so it models this as undefined.
[00:07:30] That's happening for one person, at least. So I don't know what the issue is with the file sync. Only thing I can think of is, it's a different version of Note, which completely changed how they read files in the last couple versions. So if you if you are having undefined, models is undefined.
[00:07:43] It's probably this thing failing, right now. You can just literally turn this into an array in memory versus using your file. You just get a new database every time your server restarts. So, any other questions? Yes. Kinda like a high level question, you know how you can have field specific, I forgot what you called it, but-
>> Speaker 2: Resolver? Yeah, resolver, field specific resolvers. Is it because on the query, on the capital Q query resolver, that's responsible for fetching data from whatever your data source is, but then the field specific resolver is used for modifying that data and preparing it for the client? What's the point of having these field specific?
>> Scott Moss: Because those fields can come from anywhere, like for instance, if I had a field here. Like for instance, let's say I had an image, and this image was a string. But let's say in order for me to get the image, I don't actually use it to upload it.
[00:08:40] I'll just go fetch it from some dog API. Right, that can come from anywhere, and I want to make a resolver specifically for that. Because if I didn't, the alternative would be, I have to go inside of this pets query, I have to loop through every single one of these.
[00:08:56] And then make an API call to get every single one, when in reality all you wanted was one. Or maybe you never asked for it in this field and I did all this work for no reason. So, yeah.
>> Speaker 2: I would just say it can be derived rather than stored.
>> Scott Moss: Yes, it can be derived, right? You can think of this like computer properties, right? You compute them at runtime versus having them persistant in some place. Another good example is if a user had a first name and that was a string and then they also had a last name.
[00:09:31] Like this.
>> Scott Moss: You could just make one called name that just adds them together, and you have a name, right? So you don't have to, you know what, this is in the database, I don't care about that. The front end, all they care about is name. So I'm gonna make a resolver that takes first name plus last name and return name, right?
[00:09:49] So it's computed at runtime and not so much stored somewhere. So that's a good example. And that's mostly the only time you will have a resolver. Is when you need to compute something at runtime that's different from where the source that you got it from. I know like Mongo has underscore ID, but I hate using underscore ID everywhere.
[00:10:12] So I just make a resolver for ID that just returns underscore ID. So something like that. You can do that on database level too, but I just do it in GraphQL.