Advanced GraphQL, v2

Custom Directives

Scott Moss

Scott Moss

Superfilter AI
Advanced GraphQL, v2

Check out a free preview of the full Advanced GraphQL, v2 course

The "Custom Directives" Lesson is part of the full, Advanced GraphQL, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Scott live codes a custom directive after explaining that custom directives can be challenging to people new to GraphQL. They require a definition of the schema and logic to determine when the directive is used.


Transcript from the "Custom Directives" Lesson

>> So the next part is we're gonna talk about creating a directive. And then you're gonna make one, or two, or three, or how many, we'll figure it out. So creating custom directives, they can be challenging if you're unfamiliar with how GraphQL works, because you're actually gonna be interacting with like, the lowest side or GraphQL like the AST level, you have to interact with that.

And you have to be very explicit about the changes that you make. So it's not as simple as writing a wrapper around a resolver, because GraphQL is passing you control over something that it does automatically. You have to do all the work that it would normally have to do, like adding arguments to an object and providing the actual types for something, and things like that.

So it can be kind of challenging, but you can get past it. Requires a definition in your schema. So the first thing you have to do is you have to declare a directive definition in your schema, we'll talk about that. And that you have to create logic for when the directive is use, and tell you GraphQL server about that.

So, let's follow along and let's create one. So I'm gonna create a directive here, and I'm going to call it. Let's make a directive that that logs, that logs resolvers before they resolve, they gotta be a good example. So the first thing you wanna do is, Apollo is gonna provide us with something super helpful to allow us to use directives, and it's gonna be called SchemaDirectiveVisitor.

If you've never worked on AST language type things, there's this pattern called the visitor pattern where when you're crawling an AST, which is a tree. Every time you visit a node, that's a visitor, you're visiting a specific leaf or a specific node on the tree. And you're interested in what that note is.

So you'd normally like subscribe, so when you enter a specific note, so notify me when I enter a node that is of type this, and then you'll run this callback. That's what SchemaDirectiveVisitors saying is, it's a it's going to be an implementation that allows us to run a function whenever the AST is being and it comes across of directive, it's like okay, cool, execute this functionality right now.

So that's what we're doing. So if you've never done any AST level stuff, this is the part that I was saying it could a little intimidating, but I would say GrapgQL is probably the most pleasant approach to AST level things, and some of the other stuff I've worked with.

So, pretty good. The next thing we need to do is we need to make, lets go to I type definitions and then what we'll do is we'll declare our directive. So you can say @directive, like that at the top or wherever you want to put it, and we're going to make it log, like that.

Or yeah. Directive log. And then you need to take on and then you need to say like what. You need to tell GraphQL where you want to put the directives, like you need to say I want it on the field level, I wanted on a type level, I want it on the query level, that type of thing.

So you need to declare that, and then there's like these hard coded very specific names that you have to have. So for us, we are gonna do the FIELD_DEFINITION. So add directive log on FIELD_DEFINITION. It doesn't look right. Directive log, no it's at directive. That looks right. We'll find out.

So, we'll declare a directive like that. And then now we need to do the telegraph to how to handle that directive. So we'll say, I'll make a new class. And we'll call it LogDirective. Call it whatever you want, it just needs to extend the schema directive visitor, which is a class.

And then there's a method called, in our case, because we're on a field definition. That's what we're on. We're gonna say visitFieldDefinition. Like that. That's gonna be a method. Field definition, gets a couple arguments here. The first one is gonna be filled. And I believe the second one is gonna be the object type that field is on or like its parent, but we're not interested in that right now.

And basically what we're going to start with, let's just log this field record. On this field, and the next thing we're gonna do is we'll just say field.resolve. We'll just allow it to call its resolver and resolve itself. So we'll do that. Got our log directive. Then we need to tell Apollo about that.

So we can go down to. The server. And you can pass a thing in here called schemaDirectives. And then it's an object where the key, it needs to be the same name that you declared in your typeDefs. So in our case, the name is log. So that needs to be the key.

And then a value is the class that you created, which for us was a LogDirective. Everybody following me. So you declared in your typeDefs, which will figure out that syntax is right or not. And then you extend the SchemaDirectiveVisitor. You do whatever you gotta do, we'll get to whatever we're gonna do here soon.

And we'll talk about what happens in here. This is where it gets complicated. And then you just add it to your schemaDirectives field and your Apolloserver instance with the key as the name, as the declaration and your typeDefs, and then the value as in the class that you created.

So, let's see if that works. So I'm expected, yeah. So, I think it's going to be the other way around. I think it's directive@log.
>> You're misspelling FIELD_DEFINITION.
>> Am I.
>> I after N. FIELD_DEFINITION, it's a hard one to see. Yeah, I couldn't see that. Okay, well then I guess.

Let's try that. Let's see. Try to do it without login. Yeah, I think that was it. Cool. So directive @log on FIELD_DEFINITION, looks like that executed just fine. So, let's go to our, now we actually have to use it. So we have this log directive. Let's add it on a added on ID.

So we'll say @log, we can only add it on fields, cuz we set FIELD_DEFINITION. So we can't add log anywhere else other than what we described it on which is the field. So add it on ID there, field.resolver. Actually let's just log it right quick. We'll get to that part in a minute.

Cool. So boom, as soon as the server started up, you can notice It logged that field immediately. As soon as the server startup because that filled up with the log directive on it, it went ahead and logged it and you can see how GraphQL represents a fields in JavaScript.

So you can see a couple things here. Things like that. You can see the resolver I had a rails on the find, and we'll talk about why that's on the find to with. But yeah, pretty awesome. So what we're gonna do now is we want to make a log before the resolver runs, and then we want to execute the resolver.

The reason the resolve function is undefined is because, we don't have a resolver function for a user ID anywhere. I mean, where do we have that? I mean we have user error, but we have user ID. So you might say, cool, I'll just make a resolver for the ID.

It's too much work. So, how does GraphQL automatically resolve fields when we don't make them? They use a default resolver and we're just gonna use that. So we'll say const, and we're going to grab the default resolver from GraphQL. The package called GraphQL. There's something in here called defaultFieldResolver.

A defaultFieldResolver is basically a function that takes in a value, and it looks at the keys on it, and if the keys match the field, then that's the defaultFieldResolver. It resolves its based off the return of the value. So we're gonna set that to that. So what we'll do, the strategy is basically what you do, is you override because we don't want to log.

When the server starts up, we wanna log before there was already gets executed. So we can't put our log where we just had it, cuz it'll just log as soon as the server boots up. We want to log every time that the resolver gets executed. So what we can do is we need to override what the resolver function is to our new resolver, and then call the old one.

Does that make sense? So we're basically doing everything we did in that function and authentication, where we took in a resolver, we did our own stuff, and then we call the resolver. We're just wrapping it. That's exactly what we're doing now. We're just wrapping the resolver with our new resolver, and we're gonna call it.

So first thing you did like say like old resolver or just call it resolver, whatever you wanna call it. That's going to be field.resolve, or it's gonna be the defaultFieldRresolver. And then you say field.resolve is gonna equals this new thing, like this. Then we can do our console.log of hi in and something like that.

For something, some cool, here we go. And then what we wanna do is we wanna go ahead and return our resolver. But we wanna get those args in here. So we'll say, whatever args the it gets in here, and we can say resolve.apply. Keep the same context this with the args.

I guess I should say resolver. There we go. Cool. So we made a resolver, which is gonna be the field.resolve function or the default one that GraphQL has. We're gonna override the resolve function on the field to log, and then we're gonna call the resolver that we created with the current context and the args that it was passed.

So now, what we're gonna do is. Hold up, there we go. Starts the server, everything is good there. Go back to our Playground. And if we ask for the ID. Run that. Given these variables that I click. There you go, run that. You can see boom. Hello. Pretty crazy, right?

So I mean, if you think about it, you already did this. If we go look at what we're doing here, we're basically getting access to the old resolver or making a default one, whichever one happens to be. And then we're making a completely new one, doing whatever we want.

And then calling the resolver. That's literally what we did, here inside of off, off takes in resolver. So here, this resolver that comes in. We do whatever we want. And then we call that resolver. It's the exact same thing. Except for we don't have to go rap all these resolvers.

The alternative for us is all we have to do is add a directive, or server. All we have to do is add a directive to the field, so instead of wrapping a resolver, we just added directly to the field.

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