How To Handle Errors Like A Senior Dev
Chapters7
The chapter contrasts junior and senior developers by how they handle errors, arguing for centralized, type-safe error handling to avoid scattered, hard-to-reuse code and unforeseen failures.
Smart error handling, a shared service layer, and strict type-safety turn junior mistakes into senior-grade reliability in web apps.
Summary
WebDev Simplified’s Kyle breaks down why naive, in-line error handling leaves code hard to reuse and fragile. He demonstrates scaffolding a Next.js app with role-based access, then shows how duplicating logic across API routes and actions creates maintenance headaches. The key turn is extracting error logic into a service layer and throwing or returning structured errors instead of ad hoc redirects or messages. Kyle introduces a type-safe result pattern, including a custom discriminated union (or a library like Neverthrow) to force handling every possible error state. He walks through converting redirects and toasts into JSON API responses, and how to route errors with specific handlers for unauthenticated, unauthorized, and invalid data cases. The video also covers tightening type safety with discriminated unions and the practical benefits of an ESLint Neverthrow plugin to prevent dead or ignored error paths. Finally, he notes trade-offs with redirects in Next.js and teases permissions handling as a broader senior-dev skill. This is a practical roadmap from scattered error handling to robust, maintainable error semantics that work across UI and API boundaries. — Kyle of WebDev Simplified shows how to turn error handling from an afterthought into a typed, reusable, and scalable layer.
Key Takeaways
- Create a shared create-project service to encapsulate business logic and error handling, then call it from both the UI action and the API route to avoid code duplication.
- Return structured errors instead of redirects within services, so the caller can decide how to respond (UI toast, JSON, redirects, etc.).
- Adopt a result type pattern (success or error) to force handling all possible outcomes and avoid silent failures.
- Use discriminated unions with a fixed set of error reasons (unauthenticated, unauthorized, invalid data, unexpected) for compile-time safety and explicit handling.
- Implement a simple okay/error helper (or use a library like Neverthrow) to standardize results and enable clean, typed error paths.
- Leverage an ESLint Neverthrow plugin to enforce that every error path is matched, preventing dead code and unhandled errors.
- Be mindful of Next.js redirects that throw errors; refactor to rely on returns and centralized error handling rather than ad hoc redirects.
Who Is This For?
Essential viewing for TypeScript-heavy React/Next.js developers who want to move from ad-hoc error guards to a typed, reusable error-handling architecture that spans API routes and UI actions.
Notable Quotes
"The easiest way to tell the difference between a junior and a senior developer is to look at how they actually handle errors."
—Intro explaining the goal of the talk and the senior-dev mindset.
"We have essentially a service layer that we can call in both our API and inside this action layer, and it's going to take care of all of the error handling."
—Describing the benefits of a centralized service for error handling.
"Now we have everything working. You can see the exact same function can be used both inside of this layer as well as in our API route layer."
—Demonstrating code reuse across UI action and API route.
"We can create our own custom errors if we want. I’m going to paste the code in for this."
—Introducing custom error classes to differentiate error paths.
"The really great thing about doing our code like this is now we’re able to handle each of our errors in a unique way."
—Highlighting the benefit of tailored error responses per scenario.
Questions This Video Answers
- How do you implement a shared error-handling service in a Next.js app?
- What is a result type pattern and how does it improve TypeScript safety in APIs?
- How can Neverthrow improve type safety for API responses and UI logic?
- How do you enforce exhaustive error handling with TypeScript discriminated unions?
- What are best practices for handling unauthenticated vs unauthorized errors in web apps?
TypeScriptError HandlingNext.jsAPI RoutesService LayerDiscriminated UnionsNeverthrowResult Type PatternESLint Neverthrow PluginPermissions
Full Transcript
The easiest way to tell the difference between a junior and a senior developer is to look at how they actually handle errors. If your code looks something like this, where you're handling all your errors directly when they happen, you're responding with different things like redirects, messages, unexpected errors inside of try catches, you're already doing better than most junior developers. But this can be improved drastically because our errors are sprinkled throughout this in a way that makes it very hard to reuse this code. And it also doesn't take into account any unexpected errors. And if I don't have this try catch here, for example, I now have potential errors inside my code that could break my entire application.
In this video, I'm going to show you how to fix all these different problems with architecting your code properly and making sure that you have type safe error handling, which is really difficult to do with TypeScript. Welcome back to WebDev Simplified. My name is Kyle and my job is to simplify the web for you so you can start building your dream project sooner. And to get started with understanding how errors work, let's first take a look at the application we have and the problems that it presents us. So with this application, we can log in with various different roles or as a guest, I'm just going to continue as an admin cuz that has all the permissions we need.
And I'm only going to be focusing on one particular section, which is what happens when we want to create a brand new project. So when we click on that button, you can see we get brought to a page. I can enter in a name and a description here. And when I click create project, it's going to create that brand new project for me. And it's doing that by using this create project action. All of this is built using Nex.js, JS, but all the concepts I'm going to be talking about are completely framework and even language agnostic except for the TypeScript stuff I'll talk about.
But for the most part, you can use this with any framework and language that you want. Now, if we were to try to do this with another user, for example, let's log out and try to do this as a guest user. When I try to create a brand new project and I come over here and I click create project, you're going to see it redirects me to the login page because you must be logged in for this. If I try to use it with a user that's an editor, they don't have permission to do this.
Only admins do. So, I'm going to get redirected to this unauthorized page. That's this check right here. And then finally, if I had invalid data coming along, I would get this check right here. And of course, if I had any errors inside this create project function, which is just a simple database call using Drizzle, right now, those aren't even being handled. So, I'd probably want to wrap this in something like a try catch. That way, I can make sure I handle any potential errors that come up. So, we'll just wrap that inside of a try catch.
And we'll just return a simple message that just says unexpected error. There we go. So, now at least we're handling something that happens inside of our database. you know, maybe we can't connect to our database or something along those lines. Now, at first glance, this seems great. We're handling all of our different error states. Everything is working properly. But the problem is, what happens if we want to add an API to our site? We're creating a mobile app, and now we want to be able to have users access an API to do this exact same thing.
They want to be able to create a project, but using an API instead of using this Nex.js specific way of doing things. Well, we can go ahead and we can create an API route for that in X.js. It'll look something like this. We can get the JSON body, which is our body right here. and we can get our project by creating it using some particular method and then we can return that project down to the user. Now, in order to make this actually work, we essentially need to take all the code that's inside of here, copy every single line of it, bring it into our actual new API route.
We're going to paste that down, and then we can use that code directly inside of here. So, if we just import all the different stuff that we need so that it's going to be exactly the same. We'll import that redirect. We'll get our permissions in here as well. And then finally, we'll get our post schema. Looks like my redirect was from the wrong location. So, make sure I reimpport that from the right location. And then we'll get that create project function in here as well. There we go. And now instead of our data, we're just going to use our body right here.
And now we have all of our code working exactly the same as it was before. But of course, this isn't really what we want for our API. You can see, first of all, we've duplicated our code in two places, which is obviously not good. And also, our API should not use redirects to redirect the user. It should return an error to the user. So we can have an error here for example that just says unauthenticated. There we go. And then of course we need to have an error here if it's unauthorized. So we're just going to say unauthorized.
There we go. And now we're handling all the different error states and everything inside of here the way you would inside of a normal API with all the JSON stuff. And of course we'd want to wrap this in next.json as well. So we'd wrap it just like that inside of here for all of these different responses. So, let's just make sure I get that copied over for all of them. There we go. And we'll finally close those off. There we go. So, now we have this properly working with an actual API. We can get rid of this section right here.
And there we go. Everything is working. Let's just move this up and no longer redirect. Now, already you're probably seeing lots of problems. First of all, I'm duplicating almost all of my authentication code in my API layer as well as in this action layer. So if I wanted to change something, for example, I needed to add a new check in here or I needed to remove a check from here, I would have to do that in both of these different locations, which is obviously not ideal. So instead of doing that, we're actually going to extract all of our logic for handling this into its own service.
We have essentially a service layer that we can call in both our API and inside this action layer, and it's going to take care of all of the error handling and everything for us. So I'm going to take all of this code again because essentially we're going to be using all of this code inside that service layer. I'm going to go into this services section. I already have a folder and a file for projects. I just don't have a function for the create service yet. So, let's go ahead and actually implement that. We're going to say we're going to create an async function.
This is a create project service. I just named it service at the end. So, we know that this is specifically a service. And then this is all the code that we're going to have inside of here. And our data for this is going to be our project form values. There we go. That's the data that we're using to create our project. Now let's just make sure that we get all the information we need inside here such as our redirects and everything like that and our create project function. There we go. And of course revalidate path right here as well.
There we go. So now we have everything we need directly inside here to create our project using this create project service. And now I can just directly call that in my action instead of what I was doing before. So I can essentially here just say create project service, pass it in my data, make sure I import this and now it's going to work just fine. I don't need these other imports and I can just return whatever this is or do whatever I want with this. So in our case, we could just come in here with a simple return and that would work just fine because we copied the code directly into here.
Now obviously this is not ideal though because our service layer shouldn't be doing these redirects. It shouldn't be handling this message being returned to us because we may want to handle these different errors different ways. For example, our API handles them by throwing a JSON response and our action is going to handle it by doing different redirects. So we want to handle the success and errors differently depending on where we call this code from. So what we're going to do inside of here is just throw errors that we can then consume inside of our actual response.
So what we could do here instead of a redirect is we could just say that we want to throw a new error and we can give it some text. For example, we can say Just like that. I can do the exact same thing down here for our unauthorized. We'll just say unauthorized for this one. There we go. So now we have our unauthorized error. We can do the same thing here for our invalid data. There we go. And then finally down here where we have an unexpected error. Let's just make sure that we throw an unexpected error.
There we go. Make sure we don't actually return that. And now we're handling this all properly. Obviously we don't want to do our redalidating our path or our redirect inside of here. So we're actually going to get rid of that and instead we're just going to directly return right here. There we go. So we're just returning whatever the result of our create project is and if we have any issues we are throwing those as errors. So now when I call this create service inside my action I'm going to be getting a response which is our project just like that and I can do whatever I want with that project.
So here I can just come down I can revalidate my path and I can redirect. And this works in the happy path but if I have an error I obviously want to handle that properly. So, we can wrap this inside of a try catch just like we were doing before. That's going to give me access to my error object. And then I can do whatever I want with that error object. So, for example, I could just return a message that has my error just like that. And let's make sure we just get the message property from our error.
So, now I'm able to use that error property from our message. Now, it's an unknown type and stuff like that, but at least we can actually see that working inside of our project if we want. I can come in here, type this in. When I click create, it says unauthorized at the very bottom of my screen because we get this message sent down and our UI is properly showing that as a message inside of a toast handler. Now, this again isn't ideal. It'll work great between our server and our actual client for our actions because they can both consume this.
And since all it does is throw errors, we can handle those errors however you want, but right now we're just handling them by doing the exact same thing. And since all we're doing is returning a generic error, we don't really know what the correct error is unless we look at the actual string here, which is really messy and not ideal. So instead, we can create our own custom errors if we want. I'm going to paste the code in for this. All you do is you create a class. We don't even need to export these. Actually, we will need to export them.
You just create a class. We can call it whatever we want. Authorization error in our case. Make sure it extends error. And all you need to do in the constructor is make sure it takes in a message that is a string. Pass it along as your super and give it whatever name you want. In our case, authorization error. We did the same exact thing with unauthenticated here. And now we can just use those errors instead. So here I can create a new authorization error and a new authorization error here. And for the other one I want that to be unauthenticated error.
There we go. So now we properly have an unauthenticated error. Here we have an authorization error. And here we're just using that generic invalid data one, but we could create a custom error for this as well. But we're actually going to be moving beyond this type of error handling. So I only want to do it with a couple to kind of show you how it would work in general. So now what we can do inside of our catch statement is we can say if our error is an instance of the authorization error. Well then we know that we have an error with authorization.
So we can redirect the user to that unauthorized page. Just like that. We can then do a simple check here to see if the error is an instance of our unauthorized error or sorry our unauthenticated error. And in that case we'll redirect them back to the homepage which is our login page. Otherwise, we'll just return the generic error message. Actually, we'll come in here. We'll say else oops, else if our error is an instance of just a generic error. This is just going to handle literally any non-custom error that we've created. Then we're going to return the message.
Otherwise, we'll finally at the very end return a message that just says unexpected error. And this just handles anything that can possibly come up with our application. Now, the really great thing about doing our code like this is now we're able to handle each of our errors in a unique way. Our unauthorized error is different than our unauthenticated, which is different than a generic error, which is different than just a random error that we haven't even handled at all. And the great thing about this is this code can be used both in our API route as well as in our actual action.
So, let's come into this particular section here. I'm going to take all the code that we have, paste this down. All I want to do is make sure I import the functions we're using and replace our data with our body. Just like that. We'll come in here with our redirect, get those in place, and we'll make sure we get our errors imported as well. There we go. Now, all this is working fine. And what we can do is we can actually return what we want. So here, instead of doing a return here, we want to get a next response.json send along our project.
And we're going to have a status of 2011 specifying that we created something. Now, I'm going to copy that down because in the case of unauthorized, I want to send down some JSON. And this JSON is just going to say message. And it's just going to say There we go. We can return that. I can do the exact same here, but this is going to say unauthenticated. And then in the case of a generic error or an unexpected error, we can do essentially the same thing. But again, we're going to return these as a JSON response because that's what's required for an actual API to work properly.
So now we have everything working. You can see the exact same function can be used both inside of this layer as well as in our API route layer. And if we had another layer we wanted to use this again, we could use this exact same function and we're handling all of our errors properly. Now, before we jump to actually making this what I think is truly good and type safe, I want to make sure that all the code we've written so far works properly. So, let's get rid of all these imports we don't need and let's test out this working inside of our application.
So, what we can do is we can come into our application. I'm going to log out. I'm going to do an admin first cuz they should have permission to do everything. We'll create a brand new project and you can see that we're getting some kind of redirect error showing up down at the bottom of our page. So there's a little bit of an issue going on with our code. Now the reason for this error is actually quite frustrating and that's because the way that redirect works in Nex.js is it throws an error. So this redirect error is being thrown and then it's being caught by our try catch right here, which is obviously not what we want.
So instead to make this work, we need to actually create a let variable. So we'll say project is equal to that. We'll say let project just to say that it starts out as nothing at all. We'll do our revalidate path. And then we want to have our redirect all the way down at the very bottom here to redirect us to the correct location. This would actually make our code work. And of course, we need to make sure that our project exists. So if we have a project, then we're going to do this redirect right here.
We should never get to this point unless our project does exist. So I guess we don't even need that if statement if we don't want. But now that should fix our problem. If we create our project, you can see it's created properly. So already we have some weird issues we had to deal with because of the way that Nex.js does redirects and stuff like that. Now let's log out and try this as a guest to see if this throws it proper error. And you can see we get redirected back to the login page. Try it as an editor who does not have permission and we get redirected to the unauthorized page.
So all of that is working. We can also test to make sure our API version is working. So to test how this works, I have three different requests we can send. One as an admin, one as an editor, and one as a guest. So if we send along the admin request, we should see it properly creates a new response for us. And we get the ID for the brand new project we created. We can send along our editor response and we should get an error unauthorized because the editor is not authorized to do this. And if we send along this guest request next, you can see we get unauthenticated because they're not authenticated to do this.
And if I were to send along incorrect data, for example, I forgot to pass along a description. You can see that if I send that along, we're actually getting a much bigger error for an unexpected error happening inside of our code. I believe that's just because I have a comma here that shouldn't be there. So now, if I actually send this along, you can see we are properly getting that invalid data. So I was just sending bad formatted JSON. So at least we were getting those unexpected errors and properly handling those as well. So, you may be looking at the code that we've written and you're probably thinking, well, this is obviously really good.
We have errors. We can check on those errors, do different things based on them. But there's actually a big problem with this code. First of all, let's say, you know what, we realize we don't actually need to check our user. Let's just say for some reason, we don't need to check our user anymore. So, we completely get rid of that check. We've now removed that unauthenticated error from this service, but inside of our route handler, we're still checking for it. Inside of our action here, we're still checking for it. And everywhere else we use this code, we're probably still checking for this, which means now we have code that is essentially dead.
It no longer does anything or serve any purpose, but it is still inside of our codebase. And we have no idea that this is dead code, which means it's going to get maintained. It's going to go through refactors. And this little bit of code that no longer does anything is going to cause us to spend future time and money maintaining this code, which is not ideal. Now, on the other hand, something that's even worse that could happen is let's say that we have a new type error that we need to handle, which doesn't really matter what it is, but we'll just say if math.random is greater than.5.
So, we're just going to have essentially a random error. Now, we need to handle a brand new type error. So, we could just throw a new random error. There we go. And then I can make sure I create a brand new random error. There we go. And we'll call this our random error. And we'll make sure that this just says random. There we go. So now we have a brand new random error that's being thrown. But you'll notice inside of our action and inside of our API handler, we don't get any errors if we don't handle this error properly.
So if new errors are added or if errors are removed, we get no type safety or anything telling us that we need to handle those errors properly. So, while this system looks good on the surface level and it's definitely better than the more naive approach I showed at the beginning of the video, it still has problems because there's no extra safety on the actual compile time that says that you properly handled all these different errors inside the program that it could throw cuz now we have this new error and we're not handling it at all.
Now, there's lots of different ways to fix this problem, but probably the easiest and most common is to use a pattern called like the result type pattern or a result pattern. Essentially, instead of returning our project like this, we're instead going to return a result that is either success or error, we actually already have something like this in our code. If we look at our services here, you can see we have this safe parse function which comes from zod. And you can see it returns to us a result. And this result is either going to have a success of true or a success of false whether it has an error or not.
And if the success is false, it's also going to have an error property on it. And if the success is true, it's going to have a data property. so we can properly handle what our code looks like in a successful case and in an error-based case. Now, I'm going to show you how to create your very own super simple version of this. And then I'm going to show you how to use a library to make this even easier to work with. So, let's come down here below all these different errors. And I want to create a function and we're going to call this function okay.
And this is going to create an okay response for us. Essentially, something that is successful. And then I'm going to create an error response, which we just call error. And that's going to return a result that has an error. Now, to get our actual result, we need to give it a type. And for our result type, it's going to take in two generics. We're going to have our success type and then we're going to have our error type as well. And for this success and error type responsing for our result, all I'm going to do for this type is make it an array.
The first value inside the array is going to be my success value and the second value is going to be my error. And actually, I'm going to swap those. I'm going to put the error first and the success second. That way, you're always forced to handle the error cuz it's the first thing returned to you from this result. Now, in our case, we essentially have two scenarios. One scenario we have an error and no success and the other scenario we have a success with no error. So in one case our error is going to exist and our success state here is not.
So we'll set that to null. Otherwise we are going to have no error. So this is going to be null for our error and we are going to have a success. So essentially we have only one of two states we can be in. We can either be in an error state or we can be in a success state. Now for this okay state essentially this is just going to take in some data and this data is going to be that success type. So let's make sure we type that as an S variable here. And all we want to do is we want to return the value of null.
And we want to return our data, which in our case is just data like that. And we can specify the return type of this to make sure it's that result type by saying s and never. And we're just using never because we never have an error response at all. Now our error type is essentially the exact same thing. So we'll just copy our code. In this case, we're getting our error here, which is our error. Make sure that's error just like that. Our success state is never. Our error state is error. And now we just need to swap these around.
So we have our error and then we have null for our actual success state being returned. So now by just calling this okay or error function, I'm able to convert that into a result SP response that I can use inside my code at another time. And to make sure that we're properly able to handle all of our errors and get type safety, I'm going to actually take this error type and make sure it extends a type here which is just going to have a reason on it. So we're going to say reason, which is a string, just like that.
So I'm going to make sure that anywhere I use this E variable that it's going to extend that reason string. So essentially it has this reason type that we can use as a discriminated union inside of TypeScript. Now if you're unfamiliar with discriminated unions in Typescript, I'll link a video in the cards and description that goes more in depth into them. But that's really all the code that we need to write to make our entire system work from scratch. So now up here where we have all of our different errors, we'll not even need any of these custom error classes or anything.
We can just make sure that if we have an error, all I want to do is just return that error function that we have. and then whatever the error is going to be. So in our case it has a reason and we can just say unauthenticated just like that. I can do the exact same thing down here for the unauthorized error. So we can say unauthorized. There we go. So now we have our unauthorized error handled. Let's just make sure we wrap that properly so it's a little easier to read. There we go. Let's do our random error as well.
So this one just says random. And then finally we have our invalid data that we can handle right here. So we'll get our invalid data. There we go. Invalid data. And we can even pass along some details because as I said, this result has our error on it. So we can pass along that result error and we can use that information. And with the discriminated union, we'll be able to get all the type safety for everything that comes with this. Also, finally down here inside of our catch, we should also return an error. And this error is just going to be a reason.
And it's just going to say unexpected. There we go. So that way we're handling this unexpected error as well. And finally, when we have a successful state, we just want to return that inside of okay. So we can just put that inside of our okay, just like that. And now we're properly handling this as a result response. When we hover over this, you can see we're always getting returned a promise that has this result response typing for it. You can see it's kind of explained out, but essentially we have that type for result response. So the nice thing about how this code works is the way you actually use it and consume it inside like your service or wherever you're calling your code.
It's very simple. Instead of throwing errors, we just wrap it in an error. And instead of returning our result, we just wrap it in an okay response. Now, the way we consume this is slightly different. So, let's go into our action here and make sure that we consume all of this properly. First of all, we don't need to worry about a try catch because we're already handling everything inside of here inside of a try catch. We're not throwing any errors. So, we don't have to worry about that at all, which is really, really nice. So, we can completely get rid of all of our try catch related code.
And here, when we get the response from here, this is going to give us a response just like that. And this response is going to be an array. So the first value inside this array is our error and the second value is our actual project itself. Just like that. So we have our error and we have our project. Now it does look like I have some type errors. So I just want to make sure I'm returning all my responses. Return return. I just forgot to put a return here. There we go. That should fix all of our problems.
And now we have proper typing. You can see here we have an error which is either a reason or null. And here for our project we either have that with an ID or it is null which is exactly what we expect. Now the very first thing that we can do is we can just come up here and we can say if our error is equal to null well that means we know that everything is correct and we can handle our correct state which is essentially just this. We revalidate and we redirect. And you can see by doing that my project loses that null typing because with this result type the way we set it up inside of here where it is either this or this we always know that if we have an error we don't have a success and if we have a success we don't have an error.
So TypeScript is able to actually narrow this type down for us. So we've gone through that step right now. Next step is we have an error with a reason that is a string. But right now it's just a string. We want to get around that particular issue by making this always essentially be a specific string. So to do that inside of our error type, let's create another type called reason. So we'll say r extends string just like that. And we'll use r here for our reason type. And if we make this a constant, essentially that's telling Typescript that this thing never changes.
It's a constant. So when we come back to here and we hover over our error, you'll notice that now the reason is these hard-coded strings. unauthenticated, unauthorized, random. This one has our specific details assigned to it. And this one right here is unexpected with no additional details on it. So you can see we get all these extra information for it. So we can just put this inside of a simple switch statement. We can put in our reason just like that. And this is our error reason. So let's just make sure we say const is error.reason.
There we go. And let's just do a simple case here. So we'll say case the first case. Let's do invalid data. Just like that. And now I can do whatever I want inside of here. So in the case of invalid data, we could just return a message that just says invalid data and we could pass along those details if we want because we know we have those. So we can say error.det details just like that. Now let's come up with another case here. This case is going to be our random case and we'll essentially just do a very similar thing.
This one will just return random. And we have no additional details. You can see our error is just that reason of random. So we'll completely get rid of that. Just like that. There we go. Let's copy down and do another case. This one is going to be for unauthorized or let's do unauthenticated first. So unauthenticated, redirect them back to the login page. Do the exact same thing, but this one is going to be There we go. And this one we want to redirect them to that unauthorized page. And then finally, our last last case here is just an unexpected error.
So we'll just return that there was an unexpected error. And now the really great thing about this is to make sure we have type safety. So, if we remove or add an error, we can come in here with a default case and we can say that this default case is just going to throw an error or do whatever you want. It really doesn't matter, but we're going to throw a new error because this should never even be possible to get hit. And we'll just say unhandled error. And we can pass along what that error is by saying reason just like that.
And by using this fancy satisfies keyword in Typescript, which I have a full video I'm going to link in the cards in description that covers it. It's one of my favorite keywords. But if we say it satisfies never, now this forces us to cover every possible type that this can have. Every reason that we have right inside this reason right here must be handled because if we forget to handle one of them, it's going to give us an error in Typescript and it will not let us compile our code. And it'll even tell us exactly which one we haven't handled.
And this is great because now if I go into my code, for example, inside of my services and I say, you know what, we no longer have this random error. We're going to completely remove this. When I do that and I go back to my code over here, you can see I immediately get an error. Hey, this random case no longer exists. So now I can go ahead and remove that. And now all the rest of my code inside this section works perfectly fine. Also, if I were to come in here and say, you know what, actually that random error does exist.
We need to have that. I can come back into here and now I get an error stating that I need to make sure I handle that random use case. And I can add that into here. And again, the great thing is is this works exactly the same whether we do it inside of our action or if we do it inside of our API route right here. Now, I'm not going to make you watch me type through all of the different code to convert this over to this exact same format right here, but they essentially work exactly the same between them.
And now we essentially have the ability to use one function across all different aspects of our application. And we're able to properly handle errors inside of our application by getting the error type and the response type and handle all the possible errors that can be returned to us from this by getting type safety for all of them. And that's the really crucial thing that's different between this and all the other approaches that we've done is this is fully type safe. if we add or remove errors inside of our project. This forces us to handle exactly how all of them work.
Now, to make sure it's working, let's just test it ourselves. So, we're going to first log in as a guest. We should create a new post and it should redirect us back to the login page, which it does. Try this as an admin, which should work properly. You can see it redirects us properly. And if we try this as an editor, we should get redirected to the unauthorized page, which we do. So, this is working exactly like we expect it to. Now, the next thing I want to show you is how you can do this with a library that has a lot more functionality built in.
Now, the biggest library for this is effect, but it is massive and includes a lot of other stuff. So, we're going to look at a library that just handles errors. This library is called Neverthrow. And essentially, it uses this exact same result typing stuff that we have in our application, but it's a lot more involved and has even better type safety than what we've done inside of our application. So, we're going to make sure that we install this. If we go to the install section, you can see that there is an ESLint plugin that we're going to look at, but we're also just going to install the actual Neverthrow application.
So, let's just come in here, get a quick install on that. Make sure I do that properly. There we go. And that should install this library directly for us. Now, to use this inside of our service is actually quite simple. Let's just go into our service section. And where we have our errors, we're just going to get rid of all of our custom code for the error, okay, and everything like that. And instead, we're going to be using our own errors that come directly from this library. So, we have error async. This is for whenever you have async code.
Well, in our case, our code is async. So, we just wrap everything in an error async. We're going to do that for all of our different errors cuz all of them are asynchronous. Just like that. And then we have our okay which is going to be okay as sync. So essentially we have the exact same code. All we did is just wrap them inside the functions that come from the neverthrow library instead. This is going to give us our very own result type returned from us. You can see it's a result type that's either an okay or an error result type just like we had with ours.
So if we come into here, this one instead of returning an array is going to return us a result. And if we take a look at that result, there's a lot of stuff we can do on it. But the main things we can look at is we can check is this an okay or an error response? And then we can do things with matching our errors and looping through them as well. Now the biggest thing that we're going to do with this is the match function. The match function essentially takes two functions. The first is our success response and the second is our error response.
So this very first one is going to have our project being returned from it. And then inside the response of this function. We can do whatever we want with our project. So this is essentially our successful state. So we come in here and we put all of our success code inside this section. Let's make sure it's an error function. There we go. And then we have another function. This is going to take in our error object. And then we can do whatever we want with our error object, which is essentially all of this code that we have right here.
So, I'm just going to copy this. I'm going to paste this directly into here. We can get rid of this entire section right there. There we go. Now, if we just give that a quick save and we scroll up real quick, you can see that for the most part, we're not getting any errors. We are getting a few TypeScript errors, but essentially the format is exactly the same. The only difference is we use this mass function. And this is going to give us essentially a return value just like this. If we hover over this return value, you can see that it's essentially a message with a string.
and this details. Essentially, it's just each one of these. So, we can just straight up return this response just like that and everything is going to work properly. So, now let's go ahead and look at how we can fix these errors. You'll notice when I hover over my reason, it has a type of string. That's because this neverth throw library does not by default actually handle the constant typing that we did in our own version. So, what we can do is we can just add the as const type to each one of these. And that just forces TypeScript to say that this string right here is never going to change.
So if we put as const on every single one of those different errors we have that'll fix all the TypeScript errors we have. So if we come back over here you can notice we get no more TypeScript errors. If we forget to include one of our cases you can see we get TypeScript errors for that. And if I were to come in here and I were to for example remove this entire response you can see that it removes it directly from here as well. So that gets completely removed. And the really great thing about this library is we can do more than just this match.
We can use this and then function. And the and then function in our case we're specifically going to use the async version. It gives us our success response, which in our case is our project. It allows us to do something with that and then we can continue onward and just take all the errors from this and loop them in together with all of our previous errors. So, what we can do inside of here is we can use that code for our error that was random. So, I'm just going to take that random error code. I'm going to extract that out just like that.
And inside of here, I'm going to paste that inside my and then. So, there we go. We have that error response. Make sure that I return that properly if we have an error. Otherwise, I'm just going to return my project as is. And imagine that we did something inside of here. Let's make sure this is an async response. That's okay. There we go. And we can just imagine inside here we did something else with this data that could throw an error. So, this allows us to chain together all these different errors. And the really great thing is it loops them all together.
So, this random error has been incorporated in the reasoning down here. You can see the random one is there. And if I remove that random one, you can see I get the error because it's essentially taking all my errors, the ones that came from our service as well as these and then and any other and then we have and combining them all into one. Now, you can also use this with the ESLint plugin for it. So, I'm going to show you how to configure that. So, essentially all you need to do wherever you have your config being defined.
This is with the newest version of ESLint. If you're using the older version of ESLint, you can just follow the documentation directly. But in our case, we're using the newer version. So, we have to make some changes. First of all, you need to install the ESLint plugin, Neverthrow. You also need to install eslint compat and typescript eslint parser. All three of those are needed to make this work. Then you can come down here in the plug-in section. Make sure you specify neverth throw. And you want to fix up the plug-in rules using that compat low level layer.
Inside here, you can specify the rules. You can specify them directly like this because there's only one rule inside this plugin as you can see right here. Also, what we want to do inside here is set up our language option. This is important to make sure everything works. Set your parser to that TypeScript parser that we imported right here. Set your parser options to allow M.JS. That's only if your file for your ESLint config is an MJS. Otherwise, you can mostly ignore this. And make sure that you make your TypeScript root direct to the actual main root directory of your application.
When all of that is configured properly, you should be able to get your ESLint warnings inside your editor. And you're going to get warnings essentially anytime you forget to add like a match property onto here. You should get a warning on this response object. Now, for some reason, while I'm actually recording, these errors are not showing up. This is the exact same ESL config that I used when I was testing originally, and everything worked fine. So, I'm not 100% sure why they're not showing up right now. It's probably something to do with my VS Code settings, but this is what you need to do to get that plugin to work properly to give you errors.
And that's another step of warning that you get because I can easily not handle the error just like this and everything will work fine. But by adding that plugin in, it essentially forces me to make sure I have a match or some other error handling inside my application. Now, error handling is just one smallest part of what it takes to be a senior developer. And if you want to dive into how you can handle permissions in a much better way, you can see here I have this nice permission library that has a really convenient CAN function.
And if I dive into here, you can see there's a bunch of code inside here for building out all my permissions, and it's super declarative and easy to understand. If you want to learn more about how senior developers handle permissions, I'm going to link a video for that right over here. With that said, thank you very much for watching and have a good
More from Web Dev Simplified
Get daily recaps from
Web Dev Simplified
AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.









