Server-Side GraphQL in Node.js Resolvers
Transcript from the "Resolvers" Lesson
>> Scott Moss: Resolvers we talked a little bit about it. I think this is the part where at least for me, learned about resolvers and kinda got into it. It kinda just changed a lot for me. Resolvers they're functions that are responsible for turning values for fields that exist on Types in a Schema.
[00:00:19] Resolvers execution is dependent on the incoming client Query. So I really wanna dive into that a little bit because I think it's very important of what I'm trying to talk about here. So a few things.
>> Scott Moss: One, the thing to mention is, I don't remember if I mentioned earlier on but now is definitely a good time to mention it.
[00:00:39] GraphQL, only has one endpoint. There's not many endpoints. There's there's not like a rest where you have some resource slash ID and then you want another resources and other. No, there's literally only one endpoint for GraphQL and that's it. And most of the time, you can do everything over GET, but most the time is probably gonna be over POST.
[00:01:02] And that's because GrapQL doesn't respect HTTP, it doesn't care. [LAUGH] It does not respect HTTP. If you got an error back, it's not gonna give you back a status code of an error, you still gonna get back at 200. It doesn't respect HTTP, so if you have a system based off of that, you're gonna struggle with GraphQL, you're gonna have to do some refactoring.
[00:01:19] In fact, everything with GraphQL is based off the response. So instead of getting back a status code of 400 or something like that by default, you might get back an object that has an error that tells you the code, then it hears a message. So you have to look at that stuff.
[00:01:33] But yeah, there's only one endpoint, and then the only thing that's dynamic is the Query that you send up. The reason I'm saying that is because the Query that the client sends up dictates what resolvers get executed. So the only resolvers that get executed are the ones that are for the fields that you ask for in your Query.
[00:01:49] So if I say I wanna execute the me Query and I wanna get the email. In this example, this is a Query that the client is sending up. I'm executing the me Query and I want to get the actual email. If we go and look at the resolvers that I created.
[00:02:06] In this case I created one resolver for the entire me field that returns all these fields. So I only have one resolver, I only have one field in this case. It's always going to run this resolver if I asked for it. So if I were to run this, we get back the result.
[00:02:23] But let's say I noticed I have this friends, this friends Query here. There's no resolver for it. But yet GraphQL didn't throw an error cuz there's no resolver, but that's because I didn't ask for it. So therefore didn't even try to go look for it there. I didn't ask for friends in the Query, so therefore, GraphQL didn't even go try to execute, it doesn't make any sense.
[00:02:44] The resolvers only get resolved based off the Queries that you sent. And that's basically how it works. It's going to get a little deeper than that, I just don't wanna get to it yet when we start talking about field level resolvers. But you can also write a resolver for each single field that exists on a Type.
[00:03:01] Right now we only have a resolver for a whole field called me, which is an object type but we can also have resolvers for the email or the avatar. And those resolvers won't get executed unless you ask for that field. So in this case I'm asking for the email field.
[00:03:17] If I have resolvers for avatar and friends, those resolvers won't get executed. They never will run cuz I didn't ask for them from the client Query. So this is kinda the graph nature of GraphQL. You are responsible as far as a server developer for creating nodes and how they connect with each other.
[00:03:36] And you are responsible for creating resolvers to resolve those nodes. You're not responsible for sayin, here's how you can access though, that's up to the client. The client says, here's how I want to access them. And your server should be able to satisfy that within the bounds of the schema.
[00:03:52] As long as the client sends up something that satisfies the schema validation, therefore, they should be able to retrieve it. So your creating something that flexible without having to think of every single use case. In rest you would have to think of that use case ahead of time, make a controller for it.
[00:04:08] Then eventually you'll probably just get lazy and be like all right, just pass me a Query string, right? Just give me a Query string and I'll take that Query string and I'll just throw it in SQL, or I'll throw it in Mongo or something like that. Well, yeah, that's basically what GraphQL was doing so for you don't have to think about it ahead of time the client just does it.
[00:04:23] Okay, so creating resolvers. Like what went through an early example, resolvers names must match the exact field name on your Schema's Types. So that means a resolver which is a function, its name must exactly map to what's in your Schema. So in this case I have a Query type with a me field.
[00:04:44] So my resolvers object I have a Query field and a me field and that's how it works. If I change it to something else you can bet that it won't even start up, it was like what is that? It's basically saying you have a resolver defined but it's not in the Schema.
[00:05:05] So you made a resolver called mes but the Schema knows nothing about that, so it's broke. That's something that Apollo does for you for free. It'll check your resolvers versus your crazy mutations and see if they match on start up. You can turn it off if you don't want.
[00:05:17] Don't recommend doing it. But yeah, it's basically doing that check for you to make sure that they work.
>> Scott Moss: So that's one thing, they must match exactly the same. Resolvers must return the value type declared for the matching field. So I went over this before, but in the example that we have the me field on the Query returns a user type which is an object as an email avatar and friends.
[00:05:44] And this is a string. This is a string. This is an array of users. I satisfied that. If I don't satisfy this and I ask for field then it's gonna break. For instance, if I don't return an avatar, I'm sorry if I don't return an email, which, as we know is not know.
[00:06:06] And I tried to ask for that email I'm gonna get back an error because the resolver didn't resolve that non-null value for me. So therefore GraphQL saying I cannot return null for non-nullable field, and it breaks. And that's the resolvers job. I mean, I guess you could just come here and say well, it's not non-null anymore, I just bypassed it.
[00:06:27] Well okay, you're kinada defeating the purpose. You really [LAUGH] want your resolvers to respect your schema and not the other way around. Everyone in your company will hate you if you go around changing the schema, I promise you. It is never a good thing. Guaranteed, everybody get together agree on the schema, keep it that way until you can't keep it that way.
[00:06:47] But don't go around change and stuff. People won't like it. I promise you.
>> Scott Moss: All right.
>> Scott Moss: And then resolvers can be async. And my example is clearly I'll just I was just returning an object, but resolvers can be async. So because they can be an async, you can retrieve data from pretty much any source that you want.
[00:07:10] I mean, graticule has no idea where you're getting data from, whether it's a database, multiple databases another service, a REST API. It doesn't know, it doesn't care just satisfied this shape. And if you're gonna take 20 seconds to get back and this resolver, then that's your problem. Now, you've gotta deal with the Query complexity of someone DDoS and you by doing an infinite Query or something like that, that's your problem.
[00:07:33] That's not GraphQL's problem, you gotta fix that. So [LAUGH] and that is a thing. So you can retrieve from any data source, and I think this is where the flexibility comes from. At least for me, when I saw this I was like, okay, you can pretty much mesh together a whole bunch of stuff.
[00:07:49] For instance, if you're app gets data from your data base but it also gets data from Stripe to get your customer's account status to see if they're still active or whatever. You could just put that in a resolver and expose that VN and API and have like a Stripe status on your schema.
[00:08:04] And as far as a client is concerned, it just came from your server. But what your server did is actually went to Stripe and merge it with your data and created one type. And it just looks like it came from the server. So you can do pretty interesting stuff like that.
[00:08:16] Which I think is really cool because without that it's a lot more challenging. You gotta build that system your self and the guarantees and all that stuff. Any questions on this?
>> Scott Moss: No? Okay. So a schema plus resolvers equal server. And, I said it earlier, but the minimum you need for a server is, you need a schema which has some typedefs and at least one Query on it.
[00:08:48] And then a resolver for that field. That's exactly what we have here. We have nothing more. We have one type definition, we have one Query. I don't even need this friends things here only one and only have a resolver for it. That's the minimum you need for working GraphQL API.
[00:09:04] That's it. And everything else after this is even to just more of the same or optimizations as far as speed, security, and stuff like that. But this is mostly GraphQL at this point. Like I said, other than the caching stuff that gets hard, from here it's just about getting creative about how you resolve data and how do you do advanced things, how do you organize stuff like that.
[00:09:29] But this is mostly how GraphQL works, is very simple. And if you really looked at this, this has nothing to do with the server. I mean, you could run this anywhere. If you think about if you had a schema, some type definitions, and a map of resolver functions, you could execute this on any environment.
[00:09:46] You can put this on the client. You can put this wherever you want. I know people who write CSS in GraphQL [LAUGH] it's a thing. You can execute this wherever you want. People use it to do all types of crazy stuff. So this has nothing to do with a server and data base, is more about resolving and satisfying a specific shape of data that's being asked by some third party client, or something like that.
[00:10:09] So pretty interesting if you think about it in that way.
>> Speaker 2: In a passing comment [COUGH] about string concatenation being kind of pain in the butt
>> Scott Moss: Yes.
>> Speaker 2: You're talking about all of your Queries trying to concatenate the strings together in this one file from separate files or is there-
>> Scott Moss: Yeah, that sucks too. Yeah [LAUGH] I don't recommend doing that. If you wanna separate things and put them in separate files, me, personally, I would use a .graphql file and then I would use the file system. For this type definitions thing here, you can pass an array of type definitions and it will just split them together for you.
[00:10:48] So it does have to be one string. So you don't have to do that yourself. So that's one thing. But what I was specifically talking about is if you had to create dynamic schemas on the fly, based off a model and having to generate that programmatically with a string, that is painful.
[00:11:04] You can't test it and GraphQL's very particular about spacing and all types of characters. It's very unforgiving. So I don't recommend doing that if you were dynamically creating schemas. If you are dynamically creating schemas, I recommend using the programmatic approach based off whatever language you're creating GraphQL in.
[00:11:25] So you'd generate those schemas dynamically.
>> Speaker 2: Okay.
>> Scott Moss: Yep.