200 OK! Error Handling in GraphQL

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] hey everyone i'm super excited to be here at the graphql summit talking to you about my favorite topic air handling and graphql um first off i'm sasha solomon i used to work at medium doing graphql and now i work at twitter still doing graphql of course coming from taking the first steps in graphql to a place where it's in full swing is a lot different but one of the things that was the same was error handling so let's start off like we would any good talk about graphql with a graphql query so here we're just going to query for our user uh with username ash um and we're going to query for an id and their name so let's just run this query and cool we get what we would expect back we get the data and then user id name ash ketchum so everything worked out we get what we expected so as you may know uh canonically in graphql any errors that happen are stored in the errors array they end up there by default on any graphql response this is what you can expect when you're first using graphql so you're brand new to graphql you first start using it if you encounter any errors they'll be by default in the errors array so let's just kind of take a look at what that might look like when something perhaps goes wrong so let's make that same query user username ash and query for their id and name so run it and we'll get something like this back so as you can see we still have our data field user is null and then we have a bunch of things in the errors array so this is trying to tell us that there something happened um and we can kind of start to pick out what may be happened but it's a little hard to unpack so if we look at this we can see path and in there we see user so i guess something happened with the user and then locations is trying to tell us exactly where this error occurred in the query so line two column three and then uh we have some stuff in extensions that tells us a little bit more about what's going on uh we have a message there object not found that's not super useful and then the type of this error message is uh this type of this error is two um so what exactly happened well we actually don't really know what went wrong all we have from this is object not found that's not a great error message um and it's kind of just hard to know what's going on for example if you know we queried for multiple users uh we might not know which user this is actually for without some digging and reading into this a little more like what line and column it is um and then also for multiple errors uh they would appear in the errors array also so we'd have this whole thing with user locations extensions etc etc um but then we'd also have like a whole nother thing if uh we queried for multiple things and got multiple errors so this is kind of how you get started with error handling when you first start using graphql it's kind of a lot if you're brand new to graphql it's brand new to you it's kind of hard to pick out what's going on when you actually get an error and like what do you do with it um so let's kind of look at kind of what we just saw so for one thing all errors are treated the same so all the errors any arrow we get in graphql is going to end up in this areas array no matter what kind of air it is so they all end up in there um the other thing is it's kind of hard to know where the air came from when we were looking at that errors array and looking through it was kind of hard to tell like what exactly happened um we we knew it was because it was a simple query um that it was the user and we knew exactly which user it was but for more complex queries it's going to be a lot harder to figure out where the error came from and especially if it's a list of things um and then also it's kind of hard for the client to know what errors to care about so when you're acquiring for this as a client you get all the errors in yours right so it's actually kind of hard to know which ones are important and which ones you can ignore which ones you care about and want to deal with um so that's kind of a bummer too and through all of this basically the client doesn't know what to display like that's what's most important like showing to the user like you know what went wrong or like displaying something for them and with that errors array it's kind of hard to know what to do so before we get into any like solutions or talk about anything like this let's just talk about what what is an error so let's like think about some errors so things like when we think about errors we think of things like maybe internal server error deleted user perhaps bad gateway unavailable in country a suspended user so these are kind of things that we think about when we think about errors so um it doesn't seem like all errors are the same though all those areas that we just listed so when i think about an internal server error that seems pretty pretty bad not great but that doesn't seem to be the same as say a suspended user those seem like a little different um and then also things like bad gateway that's not great either that's pretty bad and there's not a lot you can do when you're trying to recover from that especially for the client um like what do you display so that doesn't really seem the same as an unavailable country that seems like something that's maybe more normal that happens so it just seems like those aren't the same they're not the same severity they just don't seem the same type of error so that kind of leads us to error categories so let's try to categorize these errors what's what's different from them um so things like internal server error that gateway um and then deleted user unavailable in country suspended user kind of starting to group these so these things seem more like errors like something went wrong these are this is not great and then these things seem like maybe something didn't go is wrong um so something went wrong some nothing went wrong went wrong um so it seemed kind of just like different to me um and then kind of when we think about it more these really seem like errors like something actually went wrong but these other things nothing really really went went wrong but um they're not they're not errors exactly but their results just not exactly what we wanted them to be but we queried for say a user um we got something like a user back just not exactly what we wanted so when we think about that it's kind of like these aren't really errors we have things that are very clearly errors and things that are just kind of um some different kind of result really so but you know maybe that's a lot to take in right now so let's walk through why um so let's kind of walk through an example of what happens with something that we we just categorized as an error um so first off uh here's our our cute little client here and we're gonna make a request to our graphql server at the graphql endpoint there's a little graphql server and it's going to talk to say some some service or something but let's say that something is going on with that service it's like on fire not not doing great so if this happens we might get something back from that service that says ah 500 like don't talk to me this is internal server or bad um and so from that and it gets to our graphql server our graphql server might throw an exception and that might end up in the errors array so that's going to be some sort of message like server error maybe something a little nicer than that but basically this is going to end up in a 500 back to our client um so cool that's pretty clearly an error and there's not a lot the client can really do when you get a 500 except try to you know throw a nice page up there something that says 500 but the client doesn't really need to know anything else about that in order to display something um so when we think about that we would kind of say that when we get errors uh things that are in this errors category it kind of means that we we couldn't get the data that we actually requested so we didn't get the data we we asked for for some reason um so like in the case with the internal server error basically that's bad and the client can react accordingly um but basically we asked for like you know a user some data and we just didn't get that back so that seems like an error so let's talk about what we kind of just called results so let's go through this um so we have our client we're going to make a request to the graphql endpoint which is going to hit our graphql server um and then that will make a request to some number of services um so maybe we ask for like a user or something um and this uh our our service will send back something like oh um like a user unavailable in country or you know whatever some sort of something back um so when it sends this back to our graphql server and then our server sends something back to our client um you might get something back that's like a suspended user or unavailable in country so basically it's just telling the client like you know display something but it's not actually an error it's not something that actually went wrong it's kind of like oops but you know we'll still display something we'll say that that user is actually suspended or um you know this is not available in your country you know or whatever um basically whatever it gets back the client behavior might change based on that so we can actually make decisions based on what this service gave back to us if it's not something like a 500. it's something a little different um so when we think about it in that way these are sort of not what exactly we asked for they're really results or maybe like alternative results um and so when we think about it that way we did get our requested data we asked for a user and we got some type of user back um if a user got suspended the client might want to display that the user was suspended in something like an interstitial or something so you might be saying hold up i asked for a user but i didn't get a user back how is that the requested data and fair you did ask for a user and you didn't get the actual user back so it doesn't seem like you actually got back the data that you requested um but i will say that we did request a user but don't don't we care about these other states aren't aren't these things just different states of an entity or a you know a user so things like suspended user or deleted user or blocked user these are all just kind of different states of a user but they're still users a blocked user is still a user a deleted user is still a user in a database somewhere and a suspended user is still a user so it seems like if we should be able to query for these if we care about them right so of course and you know if you've if we've learned anything from graphql it's to ask for what you care about that's like the number one thing clients gets get to ask for exactly what they want and what they need and then you can get that back um so it seems like if you if you want if you want to query for a user and you actually care about what that is and want to display it we should be able to model that in the schema so let's see how we might model that okay so the schema um because who doesn't love union types um i love union types they're really cool so let's take a look at how we could use those and utilize those um so first off let's say we have a user so we have a user but let's think about the other states of a user right that's what we're kind of interested in here so we can have a user or say that user is deleted or is blocked or is suspended so these are all possible results we could have for a query um for when we query for a user during just like a normal operation so when we think about this this way really our our user is a user or they're deleted or they're blocked or they're suspended um so really that's kind of like a user result um we could say this is a user result you can get a user or one of these many things that are different states of user so really a user result is a user or they're deleted or that user is blocked or that user is suspended so when we model things this way it's actually kind of cool because now we've actually encoded all of these user states into our schema and now we can query for them which makes it pretty easy what's also cool is we can actually customize what each of these results looks like you know obviously it's not going to be a perfect user so you know it might have some other fields on here so something like maybe undeleted we just have a message saying why why you know you're deleted or whatever maybe why they were deleted or if they deleted themselves or something um for if they were blocked maybe we can have a reason and maybe some other things on that about specifically why maybe it's an enum and then if they're suspended maybe we can have a link to the policy violation um or in other fields like related to why they were suspended um so that's kind of cool so we have all these different states of a user and we can also kind of list out all the different things that are related to the states of the user that's kind of nice um so let's look let's look at what this looks like in graphql sdl which is the schema definition language um and you've probably seen this before so it should look maybe pretty familiar um so here we have just a user um and an id and a name and the id is obviously uh required cool so then what we can do is we can actually make other types that are going to be the different states of this user so we can have suspended we can have is blocked and we can have something like unavailable in country and you can kind of see how we're starting to sort of model these so for suspended we have just a string that just explains the reason you can type it in there for blocked it's going to have also a message but then we're going to maybe we can add something like you were blocked by this user and you actually could query for who they were blocked by if you needed to um and then for unavailable in country we'll have a message about what that means but also maybe the country code for where it was blocked so you can kind of start to customize these which makes this maybe pretty useful for a client and so when we take all of these types we put them all together and we create our user result which is a union so this is a user or a reason why we can't get one normally and that's going to be the types we just defined so user is blocked or suspended um we're not gonna do unavailable in country because that doesn't make sense for this user but um yeah that's pretty sweet um so what's cool is we can now query for this and the coolest thing is that clients only need to query for the data that they can use so here we have our query we have our user result requiring for ash again and this time we're going to query for a user and then if if we don't actually get a user back you know we're gonna query four is blocked and then also uh suspended so this is kind of how this works uh if you've seen this before if you use graphql a lot you've seen uh this notation but the dot dot uh we're just saying on a user so if we have a user type we're able to query for id and name on a user we're able to query for message and blocked user on his block type and obviously able to query for reason on a suspended type so that's kind of what that notation means if you haven't seen it um so that's pretty cool um so in this case we don't care about the any other things that you could query on a user result so we're not going to query for them so let's run this query and we get something like this back um so in this case this is like the happy the happy path um we just get back a user result and uh we also get back id name you may have noticed that we also queried for type name this is you don't have to query for that if you don't want to but this is just letting us know that the type that we actually got back is user and that's graphql's way of kind of telling you for union types what type you have so in this case it's a user um so that's pretty sweet um so let's see let's take a look at what happens when we get something back that we weren't uh we maybe didn't want not a user in this case um so run that same query and we'll get something like this back so in this case the the query is exactly the same but instead this user was actually blocked so we can see that here in the type name uh type name is blocked and then we see the message uh is user blocked at ash okay looks like brock and ash are maybe having a little fight or something and then uh we actually also queried for blocked by user and we can see that brock has blocked ash so that's really cool because this query didn't change but the the results that we might get back uh might change depending on if someone blocked someone um and we can see this all here we don't have to dig around in the air's race that's pretty cool um so lastly those are the two first cases the happy path where everything is great we get a user back as we expected the second case is getting a different state of that user which was is blocked so let's just look at what happens with our unexpected errors the errors we'd seen before so something bad happens so let's just run that same query again cool um and cool so now we get our this uh kind of what we saw before back uh we've got data user null um and then we have an internal server error path user and we have our location uh line and column stuff um so okay so you you might be like okay what about these fatal errors like is there anything we can do with that um these will still happen but that's okay we haven't encoded these because these are unexpected it's actually kind of hard to encode in a schema like oh if we get a 500 for this that doesn't really relate to the user it doesn't really relate to an entity so it doesn't really make sense to encode it in the schema so we won't and so it's okay that these end up here um there's not a lot that the client can do in terms of like displaying something different for the user who's using this so it doesn't really make sense to encode them um so that's okay um so what have we learned so far um with this new scheme uh we've learned that results are customizable for each entity so that's pretty cool so like a user can have different alternative results than other fields and we can also add different errors and customize fields for each type so that's pretty awesome for the other thing we know exactly where the error came from it's encoded in the schema so that's pretty sweet when we query for a user we know what if there were any um results attached to that entity because they were right there in the schema so it's because it's all encoded in the schema we are able to capture those errors those alternative results right there and like handle them that's also pretty sweet um and then lastly the client gets to decide what errors it cares about and what errors it can ignore um so that's pretty cool so a client with union types a client can query or not query for different errors um we didn't query for unavailable in country we were only querying for is blocked and suspended so that's pretty sweet um so what me that means is like if a client doesn't actually care about certain types of errors they just don't have to query for them um so that's pretty nice too um so let's kind of look at maybe what happens when our schema structure just a little bit more complex so here we have our our user result union as we did before so our union is a user or they're blocked or they're suspended and this is that sdl that we had before our user and then suspended is blocked unavailable in country types so let's add some more stuff so let's add something called profile image so we'll do kind of what we did before um we'll add a new type called profile image and we'll add different states of that profile image so things like uh maybe flagged uh maybe this profile image can be hidden things like that um and we can add a bunch more if we wanted we'll only do these um cool so we added those um so now we'll make just like we did before a union but this time we're calling it an image result so this is an image or a reason why we can't get one normally so an image result can be an image or it can be flagged or it can be hidden um so it can we can get any of these types back so let's look at how this might look all together cool so this is this is everything all together um and this is what we can this is what this looks like in the sdl cool um and the other cool thing is this is what we're going to do right now is um so we have these two different types and two different result types and all of the different states they can have and what we're going to do is we're going to actually add a field called image result called profile image result with the type image result to our user so now you can kind of see that these results can intertwine and that would totally happen a user obviously would have a profile image so this would be a profile image result so yeah what we can see here is that we've added additional result types for our new object type so we have new results for image and now we have image result which is similar to what we had with user result and now that's on that that is a field on user so let's take a look at what a query might look like so again we have our user and all our user in all of its different states in a user result which is a union and then we also have a profile image and it's a profile image result and that's on a user in all of its different states so let's kind of see like what a query for that might look like okay so um we have our user result query like we did before query for that type name just in case um and then we're going to query for user like we did before in our other queries and we're going to have id name and then we're going to query for profile image result and then all the different states that that could have so a profile image result can be either an image uh it can be flagged or it can be hidden so we'll query for all that there and then for user we're just going to query for a user and if they're blocked um so let's kind of zoom in on that because i know it's a little small and we can zoom in on your your own computers um so let's take a look-see okay so we can kind of see this a little clearer now um but it's not that much different than what we had before um it's kind of very similar to what we had for a user we're just doing kind of the same thing just different types um so cool let's run it sweet cool so in this case this is the happy path we got our data back we have our user result um and this time we've got a user back it's ash and we also have their profile image result um and the type name there is image so we got their image back and we just queried for their id the id of the profile image result cool so you can kind of see how this would look and what's great is that it's all this information is right here and this is the happy path we know it's an image cool um so again let's go through and see what happens when we don't get the user and profile image exactly how we wanted so let's run the same query again um and see what happens but maybe something's wrong with our image okay so here we have our result back our user result we still have the user back um with the id and name and this time for a profile image result we see we didn't get an image back we actually got a flagged image and the reason is because it was a copyrighted image so i don't know ash is putting up some copyrighted images probably pokemon or something um he's not supposed to do that so the reason that this profile image has been flagged is because it's a copyrighted image um so what's really cool is that this is all in line here we asked for this there's something wrong with the image and the client can display a copyrighted image or whatever based on what they got back from this query and they don't have to dig around in the errors array so that's pretty sweet um so let's just kind of go through again what we've we've learned here so again uh results are customizable for each entity so a user can have different alternative results than other entities will and we can add different errors and customizable fields for each type so we did that for user and then we did that again for image so that's pretty sweet it's great because we know exactly where these results came from we know exactly what uh what errors in the query uh what the errors were in the query because they're attached to the entity so whenever there was an issue with a user we knew exactly what the problem was because it was right there next to the user for an image when it was a copyrighted image we knew what the issue was because it was right there in line as a part of the result of our query um which is pretty awesome and then lastly the client gets to decide what results it cares about and what results it can ignore so because this is all queries and then because this is graphql as a client you can query for what you want and what your what you don't want as long as it's uh and not query for what you don't want as long as it's in the schema as long as it's encoded in the schema you get to decide so if you don't want to get all of those different types for um a result back you don't have to query for them you can just query for uh the image or you can just query for the user and don't care about anything else so you as a client get to decide what's important when you're writing your queries which is kind of the whole point of graphql and so what we learned also is that errors don't cause failures in nested queries um we were able to query for two different result types and when one failed it didn't cascade and fail the entire query this is a problem that i have experienced personally in the past it's a real bummer when you query for one thing and then something inside a nested query like fails the entire query out um so the these types of results don't actually cause these like nested uh cascading failures um the other thing is that we can kind of tune how verbose we want these results to be we can add result types to any entity we want kind of like we did with user and profile image or not we don't have to um we get to decide what we want this to look like we can add a bunch of stuff to each of our results or we don't have to add anything we can just add a field or it can just be a type it doesn't matter we get to decide how this is encoded and how this is in our schema and then also um we are able to basically more accurately represent our data by doing this our schema structure actually mirrors what our data looks like you know if we have a user and the user can have all these different states it kind of makes sense that they would be attached to the entity and encoded in our schema and when it's like that it's much easier to reason about when i'm querying for a user and you know i want to know what the different states are so i can i can display them properly so it makes it a lot easier to reason about this stuff when it's all encoded in the schema and you can kind of look at what this data looks like um so maybe you might be thinking this but uh there can there are more than just two categories of errors i just displayed these for you today um just so you can kind of take a look but there could definitely be more than just errors and things that are results um but our alternative results are kind of like a good way to start thinking about what can be returned and start thinking about how we can maybe model some of these things that might not actually be errors in our schema um so there's i'm sure there's bunches of different things um we're just not covering them today um and then the other thing is you can customize this as much as you want um that's kind of what's cool about this and what's kind of cool about graphql is that you get to decide what you care about and what you want to customize and what you don't um if you don't care about half of these states you don't have to put them in the schema and as the client you don't have to query for them um but you can also add new types of results so that's pretty sweet um so what did we learn well we learned that not everything is an error and we've also learned that you can model your errors as errors and you can model your results as results the other thing is uh results are actually things that you want to display which is awesome because we can actually encode these in the schema and we can query for them and as you may know the graphql spec intentionally does not prescribe how we do a lot of these things and this is also true of errors um so it kind of gives us gives us the flexibility to design our schema and design how we want to handle errors and in this case results the fun thing is this talk has actually been all about results i tricked you it's not about errors at all and actually it's really about schema modeling thinking about your data and how it fits in your schema and really like tailoring that to what you need and i think results can kind of help with that so one thing um medium i used to work at medium and we're actually using this uh medium is using this functionality in production right now um and it's actually helped a lot to avoid uh the error types um that we were encountering before especially that cascading issue those are all things that this you know we've used this before and we've dealt with this before and it's being used now and this is why we've kind of like thought about results a lot um i work at twitter now though and we're trying to work on using this functionality in the future it's a lot more complicated at twitter so it won't look exactly like this but we're using something similar so i hope that you've seen how we can approach error handling and how we can deal with it with different classes of errors especially when they're not errors at all we can model things in the schema and maybe they're just results um so i hope this has been really helpful helpful for everyone and thanks for coming to my talk hey everyone um hope you enjoyed the talk um and it looks like we have a ton of questions and i think we don't have a ton of time to answer them but i'm going to try to answer as many as i can um so i'm just going to get right into it um so first question um is in the case of 500 type error codes do you still recommend individual error types and that question is from kurt um and yes um basically anything that's like an unexpected error i would say like continue to use the like error codes um or put them in the airs array it's mostly because it's hard to encode things in the schema that you don't know about yet um like 500 errors would be kind of hard to encode in the schema especially when they happen for like any sort of thing um so yeah um let's see next question do you have a winter check on twitter that detects that all mutations handle all possible errors and how do you know or how do you handle new members to union types to avoid ignoring new errors and that question is from unwanted crow um so yeah i think uh one we're trying to kind of build out things to help detect when like which union arms were handled and which weren't but also um we only would want to do that for cases when clients um would need to absolutely handle all of those cases and it's really important too um but part of the like cool part of this design is that you as a client you don't have to handle all of those cases if you don't want to um so in some cases we wouldn't want to like add that sort of like lint check to make sure you're handling all those cases um cool um let's see next question this is kind of a long one and i think it's like a really good one so um on a simple query like user number of union types required might be pretty small but on a very complex mutation you might get hundreds of different possible errors it feels like you are putting the burden of knowing what types of errors onto the client however errors are purely server-driven behavior and graphql operations or driven requests um how do you deal with that how do you avoid having client operations that span a number of pages and require a huge number of fragments and how do you how do you avoid rewriting clients when a new error has to be added on the server side so yeah long question um but it's really good so one um yes like i kind of touched on this in the previous question but uh the clients will need to know what errors to handle um and but they kind of need to know this anyway um so like for example um for like a suspended user as a client i would want to know you know if that user is actually suspended and then you know i have to display that somehow so i already need to know pretty much all of these like different like entity error result types um so it's something that i would i need to know anyway so it's kind of not necessarily a burden it's like really useful because you have you must handle a lot of these things um and when you add new like union arms um yeah you'll have to rewrite parts of your client um and i think maybe i i didn't quite touch on this in the talk but um for if you're using union types to handle errors um or results um you'll definitely need some sort of like default case so for you know for a client that you know is like uh you have like certain error types or result types and then you'll have like a default kind of catch-all case just in case you know uh one of those like you get some sort of new result type that you don't expect um and that's great for when you're you know you get like a new client the older clients don't you know aren't able to support um new union arms um so then they have this default case um so that's really important anytime you're using unions just anyway because if you do get something unexpected um you wouldn't be able to handle it um so that's kind of like an important uh piece there um um and then the last one um do you think this pattern would make sense for authorization errors having a union with type unauthorized for any part of the schema um the current user can't access um this is an interesting question and i think yes um in some ways kind of like authorization errors are sort of like different states of an entity um and i think uh this sort of like union scheme could be used for maybe authorization errors um you can think of maybe some other errors maybe like rate limiting um those things we haven't been using but i could definitely see being able to use this for those things okay i think that's all the time i have so thank you everyone for all the [Music] questions you
Info
Channel: Apollo GraphQL
Views: 14,260
Rating: undefined out of 5
Keywords:
Id: RDNTP66oY2o
Channel Id: undefined
Length: 35min 20sec (2120 seconds)
Published: Tue Aug 11 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.