Check out a free preview of the full Fullstack TypeScript (feat. GraphQL & Node.js) course

The "Typed Resolver" Lesson is part of the full, Fullstack TypeScript (feat. GraphQL & Node.js) course featured in this preview video. Here's what you'd learn in this lesson:

Mike demonstrates adding types to the resolvers to all TypeScript to enforce type checking. Providing a type for the context object created for every new request is also covered in this segment.


Transcript from the "Typed Resolver" Lesson

>> The next thing we're gonna do is add types to our resolver. So we've been operating without a net so to speak up to this point. By that I mean, in my resolvers like I could put a bunch of junk in here that will not make Apollo happy.

There's nothing here that enforces, that I do the right thing or I return the right thing, and this will cause all sorts of annoying things to happen. And in order for us to have this, in order to deliver on the promise I've made, that we can have types defined in one place, and nice type checking and enforcement of those types.

All the way from our resolvers, over the wire, into our UI, like we're gonna have to address this, and that's what we're about to do next. And this is potentially one of the more exciting things we're gonna do in this project, and that is generating TypeScript code based on that GraphQL schema.

Now, everything's set up for you right now, such that you can just run yarn codegen. And we're gonna do that and then we're gonna look at what I had in place in order to make this work. So do this from within the server sub project, run yarn codegen.

So I'm gonna open a new terminal here. Yarn codegen. Sorry, I'm gonna make a quick git commit first All right, yarn codegen, and hopefully you'll see no errors, you see two green checkmarks here. You can see that some files changed so my server bounced and came back up.

And let's make sure that everything's still working. We go back to our Apollo studio, we click Run, looks like it's still good. All right, so what just happened? Well, you may notice that there is a new file, a new file appears, right? Resolvers-types.generated.ts, so you can name these files whatever you want this file, you can name it whatever you want.

I like having .generated.ts, or some convention like that, makes it very easy for me to know, I shouldn't touch this file myself ever. These are files that you should be able to blow away, and recreate based on your GraphQL schema, as your schema evolves. So if we look at the contents of this file, you can see, hey, here are our scalars.

We've got ID, we've got string, we've got Boolean. These are sort of most primitive types in the GraphQL world, and we can see a mapping of these capital letter things into their TypeScript equivalents. Here is something that looks like our query interface. And we've got our current user and our suggestions, let's look at our query resolver here.

There's current user and suggestions, so it looks like these are kind of return types of the of our resolvers. This is what our client can expect to get if asking for these kinds of things. And then we've got our suggestion and our user type great, so this represents the TypeScript equivalent.

Have these GraphQL entities, and they're all exported from this module which means we're free to import them and use them in our code, right? These are sort of your models so to speak. Down here we have more types, and some of this we're not gonna get into like there's graph skillers concept of subscriptions where you could say, tell me when something changes.

Very advanced concept and tricky to implement in a pro-formart way. But we've also got some resolver types like this resolver, resolver type wrapper. Probably have a query, query resolvers and look at that query resolvers. It's got a current user, it's got suggestion. So these are the things we're gonna use to type our resolvers.

So with that, let's get going. By the way, if you see type errors immediately following running yarn codegen, just bounce your TypeScript server. If you're using VS code, there's command restrict restart TypeScript server. It's just a lot kind of changing behind the scenes outside the context of the editor.

So VS code doesn't see that you're editing a file or saving it just something changes behind the scenes. Sometimes, if you see red squiggles, and you think they shouldn't be there, always good to restart. So at the highest level, we're gonna type the big mega resolver object that's returned with the default export from resolvers.ts.

So we'll import this resolvers type from this generated file, and we will type the return type of our create resolvers function Actually, I don't think we have a function here but we can update it to make it like that if we want. Effectively we just need the export to be typed this way, so whether you have a function that exports it or we just have a very full variable that's a default export, I think that's where we ended up.

So, let's go to resolvers, let's add this, and then let's make this of the right type. Great, so now, we appropriately be yelled at if we add a junk here which is great. Now one problem is that this query thing, if we look at what's imported, it's just sort of inferring the type from whatever is exported, which is great for this file.

But over here, it kinda is what it is, so part of the problem here is if I do this, everything kinda looks okay here. If I mess with this, make the method name wrong, I'm not getting the type errors that I would hope to get. So let me restart my TypeScript server just to be safe Yeah, I'm getting no type errors, so we really wanna type this as well, right?

So the way we're gonna do that, let me undo my deliberate messing things up here. We're gonna import query resolvers, here we go and make sure you make it a local path sorry it's dot dot slash. And now hopefully, yep, so if I don't have the right names here that are coming from my GraphQL schema, I will get the red squiggles that tell me I've been bad.

So that's it. So in resolvers.ts, we use this sort of top level type, and then for each service object that contains a set of resolvers. We can have like query resolvers or user resolvers or suggestion resolvers, and we'll talk more about when we have to build our first one.

We'll talk about why we would have that and what purpose it serves, so great There's some sample data we can return just to take advantage of our types. Instead of that empty suggestions array, we can paste the sample data in and return this instead. And what we should see, just note that there's only one suggestion here.

When we complete a change in our UI, and we actually consume this data, we should see two of these things show up. So let's return this from suggestions. All right, we're gonna just test this with our server here, or our dev tool. Okay, awesome. Note that, there's a lot more on suggestions I could get.

Get the avatar URL if I want, and you see how I'm getting what I asked for. Can ask for only the name or only the ID. All right, now let's deal with context, so This is where we have this nice little convenience object that's gonna be available within each of these resolvers.

So it's actually the third argument, it's this right here. And currently, it's an any, but what we want is to be able to do something like this, like grab the DB off of this thing. And then we could say db.getStuff, and it'll just be there handed to us just where we need it for convenience.

So, we want typing to help us understand what's possible there, and that's what we're about to do. So in our resolvers.ts, we're gonna define an interface for this context object. And for now it's just gonna have this db on it, so we're gonna have to import this, so we get its type, and create an object with a db property pretty simple.

So we're adding this to the top of resolvers.ts. And we wanna export this because we'll need it in other places. Next, we're gonna pass this type in as a typeparam to both of the resolver types we just applied. Both the one at the top level with the resolvers thing and the query resolvers.

They both take this as a typeparam, so you can add it here. And then in query.ts up at the top, we can add it here, and we'll have to import it Dot slash or dot dot slash And what does this mean? Well, now this little db thing we put in here, look at that, now we can do db., and we get createFavorite, createTweet, createUser, you had all this good stuff.

This is how we're gonna go and retrieve our data, it's really nice and convenient to have that there and had that typed for us. We're not gonna use this quite yet So we'll use her little underscore trick, we'll kind of leave it there for later. This is unused but that's okay.

Great, one last place, we need to apply this type and that is where this context object is created. Remember that little function in Apollo server, this thing here, right? We're gonna make this return a TwitterResolverContext. That means if we put extra junk on here, it will be appropriately scolded for that.

So, what is the end result that we get here? Well, if in resolvers, we say I need a thing, that's an empty array, I should be told, Hey, you got to give me that array. You have to initialize this with an empty array or something, when things start up.

And it also means that in my query resolvers down here, I could do thing, and I would get this, so here's all of the array prototype things. So let's say, I do this, right? And then go back here and we say, no, I don't want that anymore. Things should light up appropriately like this is saying, hey, this thing's gone.

So we have typing on this context object now, we also have make sure that we're returning the right things, now that we've added types to the resolvers themselves. So if we forget to return here, it's gonna say, hey, look, I expected you to give me an array of suggestions here.

Like this should be, returning an array of suggestions or a promise that resolves to an array of suggestions, does anyone wanna guess why there's this promise that resolves to an array? Like what would that let us do, the fact that we can return an array or a promise that resolves to that array?

>> Data in the middle. Yeah, we can get data in the middle, we can make it an async function, right? You just put the word async before the function, and now we can await within the function, and things will just work as expected. So, if you return a promise from one of these Apollo, we'll just wait for it, as needed, which is really nice no special ceremony required.

So, great I think we're at a point now where we have typing that works. One last check, make sure things are still operating, seems good, seems great.

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