Server-Side GraphQL in Node.js

Relationships Solution: Adding Resolvers

Scott Moss

Scott Moss

Superfilter AI
Server-Side GraphQL in Node.js

Check out a free preview of the full Server-Side GraphQL in Node.js course

The "Relationships Solution: Adding Resolvers" Lesson is part of the full, Server-Side GraphQL in Node.js course featured in this preview video. Here's what you'd learn in this lesson:

Scott continues to live code the solution to the relationships exercise by adding field resolvers, and making the querying of data possible.


Transcript from the "Relationships Solution: Adding Resolvers" Lesson

>> Scott Moss: So I know that there is no automatic resolving here, because the names don't add up, so I had to resolve it myself. So I go to resolvers, I wanna go to the pet type, because we're looking at a owner on a pet. So I need to tell GraphQL on how to resolve an owner that belongs to a pet.

So that's why I'm gonna the pet type. I'm gonna say I want an owner, with the same name, yours might be user. And this is a field level resolver. So a field level resolver is any resolver for a field that's on a type, that's other than the mutation or query.

That's how you know what a field of resolver is. So say that again, basically, if you're writing a resolver for a field that belongs to a type that is anything other than a query or a mutation, it's a field level resolver. These are top level resolvers, nothing resolves before them.

They happen first, everything comes after, which includes a field. So we're writing a field level resolver, so it means its first argument is gonna be the type that got resolved before it. In this case, the type that gets resolved before an owner is always gonna be a pet.

And you know that because an owner belongs to a pet. You can just see here, owner, pet, okay, the first argument's gonna be a pet. Same thing here, owner, pet, cool, the first argument's gonna be a pet. So I would put pet here, if I was gonna use pet, cool.

Second argument, it's the same thing. All the rest of the arguments is the same thing as every other resolver. Any arguments or inputs, we don't have any, because this field doesn't have any input. If it did, then you have some, but we don't have any here, so we don't need any inputs and any arguments.

So I'm not even gonna use them. Something's gonna block that out. And then we have some contexts here, cool. So in a real-world example, the way you would resolve this is if you would say I wanna get the context.models.user. And you would say find by ID, pet.user, right?

Cuz that pet.user has an ID of a user, you say, give me the user. That is this ID, and that's what you would do in a real world scenario. But because we only have one user database, I can say find one, I don't even need this argument. I just want you to know that this pet is the first argument that's coming in, because that's what got resolved before this fill over resolver, so just know that.

And I'm also gonna add a console.log, and I'm gonna call it owner, or let's say this pet owner, and I'm gonna return this. That's gonna help out a lot, cool. And then what I'm gonna do is I'm gonna call pets. No, let's just do one pet, I'm gonna log here.

And I was gonna say query pet, like that.
>> Scott Moss: And this is how we're gonna resolve the user for a pet, or the owner for a pet. So now if I run this,
>> Scott Moss: There we go.
>> Scott Moss: Cool, it looks like something broke. UpdatePet, I have updatePet in here somewhere.

There we go, get rid of that thing. All right, that works. Go back, refresh this.
>> Scott Moss: So now if I say, give me one pet, I don't need any arguments here. I want the name, verify that still works, cool. Now if I want the user, I can get the users, hold on.

Call the owner, sorry, that's why it's not working. If I wanna get the owners username, and I could do that, and now I get the owners username. So what I'm gonna do is I'm gonna clear this console, and I'm gonna look at what's happening, so you can see what's going on.

So I'm just gonna execute this once, and we're gonna go, and we're gonna look at this console to see what's actually happening. So the first thing that happened is we executed the top level query called pet, that's the first thing that happened. Because I asked for the owner in my query, GraphQL is like, cool, I gotta resolve that.

It couldn't automatically resolve it, because guess what? Pet doesn't have an owner field in it, so it couldn't automatically resolve it. There is just nothing it could do. So one or two things would have happened. It would have failed because it's a non-null field, or it's just gonna go look for a resolver that I registered for this field.

And that's exactly what I did, I registered an owner for this pet. So when I ask for the owner, it now went ahead and executed the pet.owner field which is this one, pet.owner. How did they know the do that? Well, because the returns the type of pet.

So it knows to go look at the pet resolvers, and the owner field for that, cuz that's what I asked for. So this is looking at the types, and it's gonna the fields. It's literally just following the clues. And it's only gonna that because I asked for it.

So if I clear this out again, and let's say, I don't ask for the owner, it's not gonna ask for the owner. You can see it only did, and everything went to the owner. So I didn't ask for it, it doesn't know what to do, unless I send it to query, that's when it knows what to do.

So now if I go back and add that, and you can see that's how that works. Any questions on that, adding an owner to a pet? No, okay, so now we're gonna do the other way around, go ahead.
>> Speaker 2: An unrelated question.
>> Scott Moss: Yep.
>> Speaker 2: Is there a way to enables this kind of slogan on those resolver calls, or-

>> Scott Moss: Yes.
>> Speaker 2: In the log somewhere?
>> Scott Moss: 100%, there is. It depends on how granular you want the logging. But yes, there's a way to automatically add logging for all this stuff if you wanna follow it. You can do middleware. I mean, you can log it, you can log in the context object.

If you want, you could add a directive, directive might be the best one. There's a lot of ways that you can do it. Are you specifically wanting to log every single resolver to see what's happening?
>> Speaker 2: Just if I need to troubleshoot some, if there-
>> Scott Moss: Just quickly turn it on?

>> Speaker 2: To turn the and to run a query and see what happened.
>> Scott Moss: No, not by default, but there's nothing stopping you. Like I said, I would say the easiest way would be to have a directive, which, you can think of directives are just middleware. Directives are just middleware that intercepts a resolver, and you can put a log before you call those over, or a log after you call the resolver.

And you can do that globally for every single resolver. But then you have to explicitly, in your schema, add that directive on every single field or type that you want, something like that, you got to do that. And then by default, it'll just log for you.
>> Speaker 2: Cuz Directive's an advanced topic?

>> Scott Moss: Directive is a very advanced topic. Yeah, we're gonna talk about that in the advanced course. But you don't need to know about Directives to use ones that are already made. So if you wanna just download a MPM install, I'm 100% sure there is a director that does logging.

That's a very simple use case, so I would imagine you could just install that and get it working. Cool, okay, any other questions on that, no? Okay, so let's do it the other way around. So now that we have owners and usernames, I wanna be able to come out here and say I want some pets here on this owner.

I wanna get this owner's pets, so let's do that. We'll come up to user, and I will say a user has pets. And it's an array of pet, and it's not null. And if you look at the database again, a user doesn't have pets. It doesn't exist, we have to virtualize it, so it's not gonna automatically be resolved for us.

So as our job, as a person who's creating this API, we need to resolve these fields. Otherwise, somebody is gonna break our API, and it's gonna be our fault, so let's do that. So I go to the resolvers, and I'll make a field level resolver for pets that exists on the user type.

So I'll go to the user type, which is down here. I'll make a field level resolver called pets, same way it's indicated in there. And can somebody tell me what the first value that's gonna be inside this field resolver for pets that exist on either type? What's that gonna be?

>> Speaker 2: User.
>> Scott Moss: It's gonna be user, exactly. It's gonna be user, because user is gonna be resolved before the pets do. And we know that, because pets is a field that exists on a type that doesn't belong to a query. And we know it's an user, because that's what's before here, and it's also what's before here.

It's always gonna be the user. The interesting part here is that there's many ways to get to a user, right? Let's think about it. We can get to a user by, if we had a query called me, and it returned a user, we can get to a user that way.

We can also get to a user through a pet, because we just made an association where a pet has an owner, which is appointed to a user. So there's so many ways that you can get to a user, that part is unimportant to you. It doesn't matter, because although you can come to a user at any point, through any path, a user is always gonna have these fields.

And that's never gonna change. And all these fields are always gonna belong to a user, that's never gonna change. So it doesn't matter how a query got to your type, as long as you resolve the fields, everything's gonna work out. And that's how it works. That's the trick, is not to think about how someone's able to do a thing, because they can do it however they want.

They can get to wherever ways they want, especially if you have these third degree, and third degree connections where things are referencing, other things that reference other things. You're never gonna figure out all the different scenarios that someone can come to your type, so you can't put that logic in a resolver.

You could only resolve a type. A type is only responsible for resolving its own fields, that's it. It doesn't care about the other fields. It just only wants to resolve its own fields, and it hopes that all the other types are resolving its own fields. Just worry about your own business.

Don't worry about my business, I will just resolve it myself. That's how this stuff works, so if you think about it that way. If you went through every single type that you have and was like, cool, database has that. Database's got that, database ain't got that I gotta write a resolver.

Cool, database got these, nope database don't have this. So I gotta write a resolver, and that's all you gotta do. And everything's resolved, you're good to go, so let's go to the pets. We know a user is gonna come here, even though we're not gonna use it. And then we still have all these other arguments that we're not gonna use.

And then we have context. So in the real world, you would do something like, and you would do a find many. And you would say I'm gonna find all the pets whose user is like, that's what you would do, right? Give me all the pets whose users use their ID, but all of them have the same user ID, so I was gonna say, just give me all of them right now.

And my example, because I did add that ID fill to them, I can do this, and it will work. But if you didn't add the ID fill to them, you can do this, and it'll just give you all the pets, which we could say belong to the owner anyway.

So either way, it's fine. But that's what you would do, so I'm just gonna do that right quick. And now we can restart this thing. Actually, I'm gonna add some logs here, so you can see what I'm talking about. And we'll say User => pets, like that. Oops, there we go.

>> Scott Moss: All right, so now if I run this, I say pets. Let me refresh this. And pets, I wanna get the name. And now you can see, let me go here. What actually happened? So we did a query on pet top level query, and then the pet resolve the owner.

And then owner's a user type, so then it resolved the pets. Everybody follow me there? All right, it's pretty crazy. And if I kept going, let me clear this console out.
>> Scott Moss: Let me clear that out. If I keep going with this approach of owner, that's owner.
>> Scott Moss: It's just gonna keep doing it.

>> Scott Moss: Let me execute that.
>> Scott Moss: It's just gonna keep going, right? So we started with a top level query, which return a pet type. We ask the owner of that pet type, and then we ask for the pets of that owner. And then we ask for the owner of that pet, and then we ask for the pets of that owner, and owner of that pet, and it goes on, and on, and on.

And then you can see here, it starts to get in parallel, where you see pet owner, pet owner, pet owner. We're gonna have a list of pets, and they all are being resolved at the same time. So they're running a parallel and then we only have one user for all the pets.

That's the one to many relationship that you're observing right now. One user has many pets, one user has many pets. One user has many pets, and that's what you're seeing right now.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now