
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
Complete source code available till this point of lesson is available at
If you have any feedback, please don't hesitate to leave a review on Trustpilot.
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
In this lesson, we explore the creation of a centralized API fetch handler to streamline API calls and manage requests more effectively. The focus is on improving maintainability and consistency across API interactions in a codebase.
00:00:02 We've created many APIs, but now we need to fetch them.
00:00:06 And of course, you can fetch them directly by using the fetch API, allowing you to simply put the URL of the API you're trying to get,
00:00:13 and then you get a response, and then you can retrieve the data out of it.
00:00:17 So if you wanted to make a request to user's ID route, you would simply make a fetch request to API users and then pass over a specific ID.
00:00:26 But this approach often leads to inconsistency, repetitive boilerplate and scattered error handling.
00:00:33 Because without a centralized solution, each API call might have different headers, timeout logic or error handling mechanisms,
00:00:41 making the codebase harder to maintain.
00:00:44 So what we need is a common fetch handler, standardizing these elements and ensuring all requests follow the same rules and best practices,
00:00:53 helping you keep your code cleaner, more consistent and easier to debug.
00:00:59 So let's head over within our lib folder and within the handlers folder, create a new file called fetch.ts.
00:01:07 Inside of here, we can create our centralized handler through which we'll make our API calls.
00:01:12 So let's export async function.
00:01:17 Let's call it fetch handler.
00:01:19 That's going to get in the T right here.
00:01:22 I'll soon explain what it does.
00:01:24 And then it'll also accept a URL of a type string as well as options, which is going to be of a type fetch options at the start equal to an empty object.
00:01:34 And it'll return a promise.
00:01:37 with an action response, and then the T right here.
00:01:41 So this is how our function declaration looks like.
00:01:44 It's a lengthy one, I know.
00:01:45 So let me explain it from the beginning.
00:01:47 We typically use the letter T in TypeScript to mention that something is a generic type parameter, just a place hold for the parameter.
00:01:55 You can think of it as a generic parameter.
00:01:57 Then we have the fetch options, and fetch options will be an additional TypeScript interface, which we can declare right above.
00:02:04 by saying interface fetch options extends request init, just like this.
00:02:11 And request init is a part of the fetch API, representing the set of options that can be used to configure a fetch request.
00:02:19 But now we'll configure it to also include a timeout property.
00:02:23 So we can say timeout, which will be optional and it'll be of a type number.
00:02:27 Now it no longer complains.
00:02:29 Now we can go into our function block And we can try to destructure some things from the options by saying const destructuring assignment is equal to options.
00:02:39 From here, we can destructure the timeout because sometimes it might be there.
00:02:44 If it's not, we'll give it a default value of 5,000 milliseconds.
00:02:49 We'll also destructure the headers and rename them to custom headers.
00:02:53 And if they don't exist, we'll make it equal to an empty object.
00:02:56 And finally, we will spread the rest of the options.
00:03:00 So we can say rest options like this.
00:03:02 Next, we can create something known as a controller, specifically an abort controller.
00:03:07 So I'll say const controller is equal to new, abortController.
00:03:14 You can see I specified something new right here, but I didn't import anything from the top.
00:03:20 That is because this is built into the DOM, allowing us to abort a request.
00:03:24 Now we can set a timeout to automatically abort the request if it takes too long.
00:03:29 We can do that by saying const id is equal to setTimeout.
00:03:35 Here we can define a callback function.
00:03:38 where we say controller data board if the timeout runs out.
00:03:42 We need the ID of the set timeout so we can later clear it.
00:03:46 Now let's get the default headers by saying const default headers of a type headers in it is going to be equal to content type will be application JSON
00:03:59 because our requests are going to be coming from the backend and typically they're made in the JSON format.
00:04:04 And we can also pass the accept property, which will be set to application.json as well.
00:04:11 Now that we have the default headers, we can define all the headers by saying cons headers of a type headers in it.
00:04:19 is equal to an object where we spread the default headers as well as the custom headers coming right here from the top.
00:04:27 Now that we have the headers, we also need to have the config for the request.
00:04:32 So let's say const config of a type request in it is equal to, we're going to spread the rest of the options and also pass in the headers.
00:04:44 And finally, set something known as a signal to be equal to controller.signal.
00:04:51 And this is a signal to support the request cancellation.
00:04:54 I know it seems like we're doing a lot of extra work just to be able to make a fetch call, but trust me, this will save us in the long run,
00:05:01 making our application that much more scalable and less error prone.
00:05:05 Finally, once we have all of this, we are ready to make a request.
00:05:09 I'll just open up a try and catch block and let's deal with the catch first.
00:05:14 I'll say const and define a new error, or we can just call it error if we call the above one err.
00:05:23 And here we first want to check if this error is an instance of error.
00:05:28 So let's create that new helper function at the top by saying function is error, which takes in a parameter of error of a type unknown.
00:05:39 because there are many different types of error.
00:05:41 And then we can say error is error and we can return error, which is an instance of error.
00:05:48 So only if an error is an instance of error, we can return an error, which is an instance of error.
00:05:53 I hope this makes sense.
00:05:56 If error is of a type unknown, then we will not do anything with it because we don't know how to handle it.
00:06:02 Right?
00:06:03 So we can say const error is equal to if is error.
00:06:08 is true to which we pass the ERR, then we're going to return the error or the ERR.
00:06:14 Else, we're going to form a new error to create an error object, and we're going to pass unknown error to it.
00:06:21 I said error one too many times.
00:06:23 Finally, we can also handle timeouts by saying if error.name is triple equal to abort error, this is the one that we give if our request times out,
00:06:36 then we'll use a logger.warn and we'll say request to URL.
00:06:42 timed out, else we can do logger.
00:06:45 And of course, don't forget to import the logger from dot dot slash logger, dot error saying error fetching URL.
00:06:52 And then we're going to show the error message.
00:06:54 And this was supposed to be named, of course.
00:06:57 Finally, at the end, we'll say return handle error.
00:07:02 So this is our centralized error handling mechanism to which we will now pass the error as action response.
00:07:10 to which we pass the generic T parameter like this.
00:07:14 I know this was tough, but to stay in check with our very complex error handling, which is scalable and always lets us know what went wrong,
00:07:23 we also have to implement proper error handling for API data fetching.
00:07:28 And finally, we are ready to use the default headers, the headers, the configuration and everything else to just make an API call.
00:07:35 By saying const response is equal to await fetch to which we pass the URL and the config.
00:07:44 Keep in mind, you could have just gone into your page and simply called await fetch and then pass the URL with the config.
00:07:50 You didn't necessarily need to do any of this, but it'll save your ass in the long run.
00:07:56 And that's why you're here to learn the production level code.
00:07:59 If the error succeeds, then we can clear the timeout on a specific ID.
00:08:05 I hope this now makes a bit more sense.
00:08:07 So we start the timer right here.
00:08:10 It's running, running, running.
00:08:12 And if the request gets successfully made, then we can clear the timeout.
00:08:15 Everything good, the request got made.
00:08:18 But if it waits for more than five seconds and it doesn't reach this part of the code, it's just going to cut it here, okay?
00:08:25 So this is going to prevent our app from trying to make requests that take too long.
00:08:28 Finally, if not, response.okay, we're going to throw a new request.
00:08:35 error coming from HTTP errors to which we need to pass the response status as well as a string that's going to say HTTP error response dot status.
00:08:48 Finally, if we don't wait for five seconds and if the response is okay, we're simply going to return await response dot Jason.
00:08:58 which contains our actual data.
00:09:00 And this is it.
00:09:01 You can see that no longer it complains right here because it knows we're returning a promise, which then results in an action response that has a success field,
00:09:09 which is a boolean of true or false, a data field containing all the data, and then if there's an error, it has the error and the corresponding status.
00:09:18 And I know this was complex and that you might not be able to wrap your head around it on the first try.
00:09:23 So let's try to explain it once more together.
00:09:26 I'll select the entire code, I'll open up the copilot chat with command shift I, and I will ask it to explain this code.
00:09:35 So we can go over it one more time together with the help of artificial intelligence.
00:09:40 There we go.
00:09:42 Okay.
00:09:43 See?
00:09:43 We wrote a lot of stuff.
00:09:45 Let's now go over it.
00:09:46 This code defines a fetch handler, which you can see right here.
00:09:51 A function that wraps the native fetch API to provide additional features such as timeout handling, custom headers, and error logging.
00:10:01 Here's a step-by-step explanation.
00:10:03 First, we import a couple of stuff.
00:10:05 We import the request error from HTTP errors, which is a custom error class for handling HTTP errors.
00:10:12 Then we import the logger, which we have done in one of the previous lessons for logging.
00:10:17 And then we also import the handle error, which does exactly what it says.
00:10:21 Then we have the interface called fetch options, which extends the base request in it and adds an additional timeout property.
00:10:30 There's also a type guard function, checking if a given value is an instance of error.
00:10:36 Finally, we have the fetch handler function itself, which takes in a URL, which we want to make a request to, and an options object called fetch options.
00:10:46 Then it destructures the options object to extract the timeout, the headers, and other options.
00:10:52 We then set up an abort controller to handle request timeouts.
00:10:57 Finally, we merge the default headers, which we need to have, with the custom headers, which we might want to pass into the function.
00:11:05 Finally, we configure the fetch request with the merge headers and the abort signal.
00:11:11 Then we try to fetch the request URL by passing the URL and the configuration.
00:11:16 If the response is not okay, it throws a request error, but if successful, it returns a response JSON, meaning the data.
00:11:24 Finally, in the error handling part, we check whether an error is an instance of error.
00:11:29 If not, we set it to be an unknown error.
00:11:32 But if it is, we check if it's an abort error, which means that it is a timeout.
00:11:38 And if it's not an abort error, then it's something else, which means that we simply log the error message and call the handle error function.
00:11:45 In simple words, this function ensures that fetch requests are handled with proper error management and logging, and it supports request timeouts.
00:11:53 If it's still confusing, don't worry.
00:11:55 It's going to make more sense when I teach you how we can actually use it to make API requests.
00:12:01 That's what it's for after all, right?
00:12:03 So let me head over to the lib folder and create a new file called API.ts.
00:12:10 This is how you would call our fetch handler.
00:12:13 You would simply say something like fetch handler, paste the base URL path, and then append the additional path you want to call,
00:12:20 like accounts ID.
00:12:22 As the second parameter, you would pass the config options.
00:12:25 Okay, check this out.
00:12:26 The first parameter is the URL, same as we specified right here.
00:12:30 The second parameter is going to be the options, same as we specified here.
00:12:35 In those options, you would pass something like your HTTP method and the body.
00:12:40 If it's a put method, you need to submit some kind of data.
00:12:43 So you typically put it within the body right here.
00:12:47 While this is cool and all good, we can create another centralized layer that manages all API requests, abstracting away the complexity of constructing
00:12:57 each individual fetch request, allowing you to turn this into something more like this.
00:13:02 API.createAccount to which you pass the account data.
00:13:08 I mean, check this out.
00:13:10 This would be super pretty if we could do something like that instead of this.
00:13:14 And it's not just a single request.
00:13:16 There's going to be dozens.
00:13:19 As you can see here in the app API, we already have many different endpoints.
00:13:23 So if we can simplify our life and call them like this, instead of calling them like this, that would be amazing.
00:13:30 So let me teach you exactly how we can do that.
00:13:33 You can say export const API and make it equal to an object where you can define all of your routes.
00:13:41 For example, for the users, you can say users is going to be an object where you can have a get all method on it.
00:13:49 This getAll method is going to be equal to a callback function where we're going to call the fetch handler, same as before,
00:13:58 and to it we'll pass the API base URL.
00:14:03 So first we have to construct this API base URL, which we can do at the top by saying const.
00:14:10 API base URL is going to be equal to process.env.next underscore public underscore API underscore base underscore URL.
00:14:21 So it's going to be coming from our ENVs.
00:14:24 Or if it doesn't exist, we can simply put it to localhost 3000 forward slash.
00:14:30 API.
00:14:30 We do it this way so in production we can actually pass a different URL and it'll automatically change so we don't have to manually change every single
00:14:39 route from our local version to our deployed version, immediately breaking the other one.
00:14:45 This way it's super scalable, it works locally, and it works in production.
00:14:50 To this we can append a forward slash and then simply go for the user's route.
00:14:55 After the get all, we can also try to get one user by ID.
00:15:00 For this one, we'll need an additional parameter of a type ID so we know which user to get.
00:15:06 So that's going to look something like this.
00:15:07 We're going to call the fetch handler with the API base URL, which is the localhost 3000 forward slash API, head over to forward slash users,
00:15:16 and then append the dynamic ID property to it.
00:15:19 Let's also do one more for the users to get them by email.
00:15:23 We can do this by saying get by email, which is going to accept an email as a string.
00:15:29 It'll call the fetch handler like this, and it's going to go to API base URL users forward slash email.
00:15:37 But this one is a post request.
00:15:39 So instead of appending it like this, we'll actually pass an optional options per RAM where we can define the method of post And the body is going to be
00:15:50 JSON.stringify where we pass an object with a value of email.
00:15:53 Let me show you which one this is.
00:15:55 So if you head over to API, users, email route, and check this route out, you'll notice that here, this is a post request that accepts an email through
00:16:08 JSON body.
00:16:09 And I just noticed that here in the try block, I forgot to connect to the database.
00:16:13 We cannot make database calls without calling this await DB connect before.
00:16:20 So definitely make sure to import it and add it right here.
00:16:22 Now I'll head back over to the API.
00:16:25 And let's try to add a few more methods for users specifically.
00:16:30 It's going to be a create method where we accept the user data, which is going to accept a partial of iUser.
00:16:38 This is the interface of a type user.
00:16:41 And we can return a fetch handler to which we can pass the API base URL pointing to forward slash users.
00:16:50 and wanna pass the options object where the method will be post and in the body will JSON that stringify the user data of the user which we wanna create.
00:17:01 And we need to get the interface of the user from database user model.
00:17:05 Now we need to do a very similar thing for the update.
00:17:09 So I'll add the user update right here, which is gonna accept the ID of the user which we wanna update, the partial user data to update that user And then
00:17:19 we're going to call the fetch handler, pass in the API base URL pointing to forward slash users, forward slash ID.
00:17:27 And then we'll pass an additional options object where the method will be put and we'll send over the stringified user data to update our user.
00:17:36 And I love how Prettier makes this so much more readable.
00:17:38 And finally for the users, we have the delete.
00:17:42 So the delete method will accept the ID of the user to delete.
00:17:46 And then it'll call the fetch handler, pointing to forward slash users, forward slash ID, and simply passing the method of delete.
00:17:54 And this is it.
00:17:55 These are all the user methods.
00:17:57 As you can see, if we collapse them, get by email, create, update, and delete, everything is in one place, nicely contained.
00:18:06 And just very quickly, we have to do the same thing for the accounts.
00:18:09 Remember, we have all of the routes right here for the users, but most of them are similar for the accounts.
00:18:15 So what I will do is I'll copy this entire user's object, paste it over, and I'll change it over to accounts.
00:18:24 It's going to be very, very similar, but wherever we're saying users, in this case, I'll rename it over to accounts.
00:18:32 You can see it is in one, two, three, four, five, six different places right here.
00:18:37 So let's rename it to accounts.
00:18:40 And let's do one final check together to see if it's good.
00:18:44 Get all accounts.
00:18:46 Get account by ID.
00:18:48 This is not going to be get account by email.
00:18:51 Rather, we're going to change this to get by provider.
00:18:55 You know, whether it's GitHub, Google, or something else.
00:18:58 And we're going to accept in a provider account ID.
00:19:02 So we're going to make a request to accounts forward slash provider.
00:19:08 And a post method where we pass over the provider account ID.
00:19:12 Then we have a create where we try to create the account, but be careful.
00:19:17 It's going to be a partial of the I account, not I user.
00:19:21 So fix it here and here.
00:19:24 And sorry if I'm jumping a bit too quickly right here.
00:19:26 I'm just trying to modify it and this is how I would typically go.
00:19:31 If something is repetitive, try to refactor it.
00:19:34 But in this case, we truly have two similar things, accounts and users.
00:19:39 So I copied everything and now I'm making minor tweaks.
00:19:42 Now I remember that when I created this getByProvider function, Since I was copying so much from the users, I actually left some incorrect code in the
00:19:52 get provider function.
00:19:53 So if I head over to accounts, provider route, let's go ahead and fix it because some of you on Discord actually noticed this error.
00:20:03 So thank you for letting me know that.
00:20:05 I'll add the await dbconnect right here at the top.
00:20:10 That way we can successfully fetch the data.
00:20:12 Now we can head back over to the API file.
00:20:15 Look into the create this time.
00:20:17 We're not getting the user data.
00:20:19 It's going to be the account data.
00:20:20 So I'll actually replace it in all four of these places.
00:20:24 That's going to be account data.
00:20:27 We get the partial of the account and create a new account in the same way we update the account and delete the account.
00:20:36 This is calling the function under accounts and then ID.
00:20:40 And you can see the delete right here.
00:20:43 If you go into it, you'll notice that when we're deleting the account, I actually said user dot find by ID and delete.
00:20:50 And this is something that can happen when you're not careful and when you're just copy and pasting stuff over.
00:20:55 Always make sure to double check.
00:20:57 So this right here, of course, was supposed to be account dot find by ID and find by ID and delete, because we are right here within the account route,
00:21:08 not user.
00:21:09 So once again, thank you great Discord people that pointed this out.
00:21:13 And with that, we've done a lot of work, but trust me, it will all have been worth it.
00:21:19 We have created two API controllers, let's put it that way, that allows us to simply say, hey, API users get all, give me all the users,
00:21:27 that's it.
00:21:28 You don't have to mention the actual paths to these API routes ever again.
00:21:34 Let me show you how this works.
00:21:36 I'll head over to our root page.
00:21:38 That's going to be the one under app root page.tsx where we had the test for error handling right here within this test function.
00:21:48 I will remove this throw new validation error.
00:21:51 And instead I will say return await API, which we get from lib API, which we just created dot users dot get all.
00:22:04 This is it.
00:22:06 Believe it or not, you don't have to do anything else to make an actual fully validated, fully error handled, fully normalized request to our backend server
00:22:19 and therefore to our database.
00:22:21 API users get all.
00:22:23 It is as simple as that.
00:22:25 So if you do that and head over right here where we're calling the test, here we're going to get the actual users by saying const users is equal to await test.
00:22:35 And now we can simply console.log the users.
00:22:38 Let me show you how that works.
00:22:40 Just open up your terminal and check this out.
00:22:43 We have our great logging functionality.
00:22:46 So as soon as you reload the page, you can see get API users 200, which actually took 40 milliseconds because we used an existing Mongoose connection.
00:22:57 We got back an object containing the normalized structure, which every time we know what to expect, it's going to have a success of true.
00:23:04 And then the data, which is an array containing all the users, currently just a single user called John Doe.
00:23:12 In my opinion, this is just beautiful.
00:23:16 I know, I know, it took some work to do.
00:23:20 First, we had to create this fetch functionality, the fetch handler, that is basically not doing anything else than just making a simple fetch.
00:23:29 But it is wrapping the whole fetch functionality and normalizing it for us.
00:23:35 What does it mean to normalize it?
00:23:36 Well, so that we can always expect what happens.
00:23:39 Even if it's an error, we'll know how to handle it.
00:23:42 Or if it's a successful response, we'll know the shape of the data that we're getting back.
00:23:48 Then we could have stayed there and just head over to the homepage and try to do something like const.
00:23:55 Let's say users is equal to fetch handler and then make the actual request to URL, passing the options, and we could clutter the URL.
00:24:07 And we could do this for every single request.
00:24:10 But no, we went a step further and created this API file that contains all of the different routes for all of the different route branches within our application.
00:24:22 Right now, just the users and accounts, but I think you can see how easily you can scale this.
00:24:28 We create a function that you can call such as API users get all.
00:24:32 It could, but it doesn't have to accept some additional params.
00:24:35 And then we call the fetch handler, give it its first parameter, which is the URL, and potentially sometimes also a second parameter,
00:24:43 which is the options object.
00:24:45 And then we can call it from any kind of page in a simple, single, straightforward line.
00:24:50 This lesson right here.
00:24:52 Coding out all of these files is the reason why you're taking this course.
00:24:56 The production level efficiency, scalability, and making your code less error prone is what will make you a better developer.
00:25:03 So let's say right here, implement a fetch handler.
00:25:08 commit and sync, and soon enough, you'll be able to make the requests very easily now to all of the API routes that we created.
00:25:17 Great work.
00:25:18 Oh, and just before you take your well-deserved break or continue to the next lesson, if you don't mind, it takes just a couple of seconds.
00:25:26 I'll leave the link to the JavaScript Mastery Trust Pilot below this lesson.
00:25:29 I would very much appreciate it if you could come to this page and just leave a quick review.
00:25:35 It doesn't have to be long, but please don't make it generic.
00:25:38 Make it something specific about this course.
00:25:41 Like if you had a thing that you struggled with for such a long time, but then through one of the lessons in this course,
00:25:47 it actually finally clicked.
00:25:49 That would be the perfect type of feedback that I'm looking for.
00:25:52 So if you have it, just click the link, give a feedback.
00:25:55 And with that said, let's continue.