Server-Side GraphQL in Node.js

Relationships Demo

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 Demo" 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 demonstrates how to use field level resolvers to write relationships between types.


Transcript from the "Relationships Demo" Lesson

>> Scott Moss: Okay, so see what else I have here. All right, so before I set you off, let me show you an example of how this works. So, like I said, I added the shoes to the user and I added the- let me get rid of this union right quick.

Get out of here, too much stuff, too much stuff happening. So what I'm gonna do now is let's go down to shoes and we'll put a user ID here, I know I put up there a user must be a user type which is an object, but I'm just gonna put an ID here of user 1 and I'm gonna put an ID here of user 1.

Cool, they all have user 1, and then what I'm going to do is I'm going to make another resolver for a new query that I'm gonna make, I already have it. Neat, cool, I already got it. So for me, I'm gonna return all those user stuff but I'm gonna put a id of one like that.

Cool and users have shoes now, so I'm gonna put shoes there like that, which is empty array. All right, so we got empty array here, we got a user of one.
>> Scott Moss: Did I spell this right, shoes, shoe, shoe. Okay, cool. This broke because- I've still got footwear somewhere.

Where do I have it? There it is.
>> Scott Moss: Okay, this one broke because I still have footwear in my resolvers. Look how strict that is, gotta love it.
>> Scott Moss: There we go.
>> Scott Moss: And it broke again because shoe.user- I still have the interface, where is that at? So now it's basically saying, hey, you added a new field called user on the interface but you didn't add it on all of them.

So this is what I was telling you, it's really explicit. You have to add them everywhere, it does not care.
>> Scott Moss: And there we go. Now we're good to go. So, if we go look and explore this, it's gonna get a little crazy, refresh this.
>> Scott Moss: If I said, give me some shoes and I asked for a user, let's just see what happens.

So clearly this is gonna break, because it's saying, fill user of type user must have a selection of sub fills, did you mean user? Well, I only put an ID of one on those shoes' user, so therefore if I try to do anything here, it's not gonna do it, because it can't get a .email from a number, it doesn't work that way.

So I had the right resolvers for those fields. And typically what I found out is what you wanna do, there's a lot of ways to do this. But to save yourself a headache, so let me tell you what not to do. [LAUGH] What not to do is to like, okay, cool, I got all these shoes here.

I'm gonna go to back to the database or my data source, take all these user IDs and go get those. And then put them here and when I return this object, it's gonna be good. Correct, it will be good. But what if I didn't ask for the user in the first place?

You just went and resolved a whole bunch of users of a list of 1000 shoes for no reason. I didn't even ask for the user. How do you know that, if you don't know what I'm asking, why go do the work? Leave that up to the resolvers. The resolvers are only gonna execute if you ask for it.

Why re-implement that logic? The only way that you can determine what someone asked for if you're already inside a resolver is if you look at this fourth argument here, like I told you, info, that's the only way. If I looked at this AST, I could determine, they didn't ask for users.

So I'm not gonna go back to the database and try to resolve these user IDs. That's the only way you can do it. But again, why reinvent that wheel when GraphQL already does it for you? So all you have to do is just make a resolver for a shoes user object, that's all you gotta do.

So if I go down to shoe, I can make a resolver for its user field. And I know because it's being resolved underneath the shoe type, its first argument is gonna be shoe. And I know that shoes have a user ID on them or whatever that is, so I can just return, and I'm just gonna hard code this user here.

Let's go ahead and take this user like this, so we'll reuse it.
>> Scott Moss: Boom, we'll hard code that user like that. So now I can just return, and this is where you would say, find user by shoe.user, but I'm just hard coding it right now, so I'm just gonna say return user.

So then now you have to resolve this on the actual type and not the abstract type. So this is where it gets complicated. So instead, we'll say, let's resolve this on a Sneaker. And we'll say a user, like that. And I'll do the same thing for a,
>> Scott Moss: Boot.

>> Scott Moss: Like that.
>> Scott Moss: Cool, so if I run this, then boom, you can see, yes?
>> Speaker 2: So, earlier when you were doing the union, if you go back there to the code, would you be able to do the resolver on the union?
>> Scott Moss: Would you be able to put, no.

You can't do resolver in the union, you can't write field level resolvers on abstract types, they have to exist on the real types.
>> Speaker 2: Okay but you're [LAUGH] returning something off of Sneaker or Boot and those are abstracted.
>> Scott Moss: No, Sneaker and Boot are real types, so let's go look.

>> Speaker 2: They're real because they're-
>> Scott Moss: They have the word type in front of them.
>> Speaker 2: Okay.
>> Scott Moss: Yeah, anything with the word type in front of, it's a real type. Anything that is an interface is an abstract type. A union is also an abstract type.
>> Scott Moss: Cool, so obviously this is redundant, it's the same thing.

So this is where failover resolvers, I actually have those in separate files because I wanna reuse them all the time. Like, I'm gonna make this field level resolver for every single thing that implements this interface, why do I have to write it ten times? We'll take this function out, put it somewhere else and import it.

And that way, you don't have to rewrite it. But yes, it's the same thing. Okay, so that's one level, sand we should not be able just to do this now.
>> Scott Moss: Cool, so now we're able to get a users from the shoe but users also have shoes, so if we set shoes and now we wanna get the ID or the size of that shoe and I ran this, chooses it in the array.

But we know shoes isn't in the array, so let's fix that. So now what I'll do is I'll take these shoes here, like this, and I'm just gonna say, return the shoes.
>> Scott Moss: Some shoes here like that. Now what I need to do is I need to write a resolver for the user's shoes.

So it can get back in array of shoes. So I'll just erase that one there. I'll keep it, cuz it doesn't matter, I'm gonna overwrite it.
>> Scott Moss: And now, I'm just gonna make a new type for user like this. And I could say, give me the shoes for the user.

And I'm just gonna return shoes. But normally what you would do is you would take the user from the first object. You will go to your data source and like, give me all those shoes that have this user ID. Right, that's what you would do. But I just have all these code, and so it doesn't matter.

>> Scott Moss: Now, if I rerun this again, you can see, okay cool, so I got some shoes, I got the users for that shoes, and I got that user shoes, and I got the size. Okay, we'll check this out. So if I say, cool, give me the user, give me the shoes, give me the user, give me the shoes, give me the user.

Give me the shoes. Okay, you can see this goes on forever.
>> Scott Moss: Well, this broke because they want something. Now finally give me the size of those shoes. Cool, there we go. This goes on forever. And this wasn't that bad because honestly, I've seen [SOUND] so much worse than this.

And all this is synchronous, it's in memory. Imagine if every single one of these was a database call, or a rest call or something like that. [LAUGH] And imagine you were getting a lot of these queries a second, hundreds a second. Just think about what would happen to your server.

It would be ridiculous, but this is the cyclic nature of GraphQL. If you allow it to be cyclic, and how do you know something is cyclic? Well, if you make a field whose value's a type and that type also has a field whose value is the previous type, that's a cyclic dependency, a direct one.

You can also have an indirect one where you have a type referencing a field on another type but that field references are completely third type, but that third time references the first type. And now you have a cyclic [LAUGH] dependency that's not direct. So there's really not much you can do about it.

So I wouldn't say just maybe don't design your data that way, I wouldn't say that. I would say, you should design your data that way, it's a great thing, that's the whole point of GraphQL. And the solution is with inside the resolvers and the caching strategies and the security limiting depths and adding scores.

It's not a data modeling problem. Don't do that. If you try to restrict how you can model data in GraphQL, you're gonna miss the point of GraphQL. You're gonna completely miss it. So, don't do that. Solve it on another level. But yeah, that's how this happens. As you can see, it gets pretty crazy.

I mean, even with this, I could probably get this to break,
>> Scott Moss: Pretty easily, let's see, bet if I did three more levels, it will just crash or at least it'll be super slow.
>> Scott Moss: Let's see, it's crushing it. [LAUGH] You can get this to the point where it'll take a couple seconds to come back even in a memory.

I've had it. But yeah. Everybody see the issue that I'm talking about here? This is what I mean by DDoS. And remember, this is one request. GraphQL is only one request. This isn't multiple requests. So a CDN wouldn't help here. Cloudflare would not help here. The cloud's like, what are you talking about?

It's just one request. I don't know what you mean. Cuz this is application level, it's not network level. GraphQL doesn't care about https. Whatever, I don't care about that. I'm issuing many things on one request. So, something to think about here. But that's relationships in GraphQL. So in short, resolve, write field resolvers for relationships.

They work really good.

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