Many times when we build a website or web app, it’s often likely we’ll need to make an external call to an api service on our frontend. However, as the project grows in complexity, it is a good idea to separate these fetch api calls away from your application’s core logic.
When doing this, there are a few nuances you will want to consider, such as how you choose to export your fetch api call functions, provide type-safety for the request body of a fetch api call, the expected JSON response schema returned from one, etc.
This post will cover important areas like this, as well as pretty much anything else you'll need to know to confidently organise your fetch calls – at least in my humble opinion 🙂
Also, if you only know JavaScript at the moment then you can still follow thorough with this guide, as it only means less work for you to do since you don’t have to worry about types and can freely ignore them.
Folder Structure
To keep this guide short and beginner-friendly, we’ll focus our examples for a small (potentially) scalable application. If you wish to apply this framework to anything on a larger scale, improvise with the examples provided and use them in your own context.
For example, if you have a modules
folder that has a folder which contains all files relevant to a specific module – i.e. it’s components, utilities, interfaces/types, and so on – you would put your api service related fetch calls inside that module’s folder using the structure I'm going to teach you.
Naming the folder “services”, “api”, or what not – is not important – however what is important, is deciding whether you want to export all your fetch calls in a single index.ts
, or have them exported separately from different folders which couple together related fetch calls for a particular service; for instance, a “users” folder would be related to a service which performs user-related requests (calls). This project in particular, doesn’t make sense to create any separate folders at the moment since we are not making use of any api services to begin with!
In that instance, I have an empty index.ts
file with no exports. If I need to add a new “service”, which I'll demonstrate shortly, I'll put it in the index.ts
file rather than creating a separate folder for it.
What I like about this approach is that if you anticipate using at least three api service fetch calls, you can simply add a folder with an index.ts file to export the related fetch calls, and then re-export them into the root index.ts file of the services folder.
I’ll introduce a recommended file you will want to add in your folder structure, which is a helpers.ts
file that is responsible for sharing useful methods and types – as the name implies, it’s quite “helpful” 😅
Refactoring Fetch Calls
If you've previously refactored your api service fetch calls already from your application, feel free to skip this section, since this section will cover an example of how you can go about it using the preferred approach I take.
To begin, search for areas in your own application where you are making a fetch call to an external api service. I’ve created this example on the fly to demonstrate this, but essentially you would want to create a function to refactor the logic for fetching a particular resource from an api service.
A good rule to consider is to restrict an index.ts
file to fetch only from a single api endpoint; for example, to adhere to this rule I make sure to have a constant that reserves a variable in the scope of the file. Therefore, you may start with your rooted index.ts
file from the services
folder to store your fetch call functions, and then refactor them later on to their own respective “service” folders.
Exporting Our Services
Now that we’ve got a file with all of our refactored fetch api call functions – what’s next?
Seem obvious right, we simply just export them to be used in our app…
Well actually, there are two ways to do this: one, export these services as a class (or an object); or two, export them separately so that you may import only the api service methods (functions) you need.
The question is, which is perhaps better and why?
To be honest, this comes really down to preference, but the one major key thing to distinguish is how the api services are being imported and used throughout our app.
If you export everything as individual fetch call functions, you may run into trouble later on if two functions have the same name. Therefore a naming conflict, and so a way to prevent this would be to include a unique prefixed group name to these sets of related fetch call functions, for example, naming your call functions fetchSupabaseUser
and fetchSupabaseUser
to not conflict with fetchFirebaseUsers
and fetchFirebaseUsers
- this is really a bad example, but you get the point!
Hence, you can also argue that you would most likely not encounter these sort of name conflicts due to the really absurd nature of the use-cases of where this would apply.
However, with that said, I prefer to use the later approach of exporting the services in an object because it allows me to do two interesting things: firstly, make my object read-only so it can only be accessed but not for modification (application of the “Open Closed Principle”); and secondly, be able to name my object so it's clear what sort of api service methods (functions) you expect.
Creating Helpers
To make our file more "cleaner" and type-friendly, we can add explicit typing for our fetch api call functions and introduce some helpers that will come in handy later on if we decide to add more call functions.
Firstly, we’ll install the package zod
which will be used for our type-safety in our fetch api call functions – you may use a different schema validation library, but be sure to adapt the examples accordingly.
Depending on your application, you may want to consider having a custom error constructor that extends the native Error
class. This makes it possible for you to extract any additional information from an error response, such as the status code and message used in this example, to provide more context to your users when they experience errors (or just for your analytics).
Note: You may want to handle all errors instead of the just the first error as show in the code snippet!
For each endpoint you call, it’s probable for you to encounter an error response, especially if your dealing with authenticated-based fetch calls. In that scenario, you will want to make sure you have a dedicated error handler that deals with this, taking into account of the expected error JSON response schema. In our hypothetical example, our api service uses the JSend JSON response schema.
Make sure to also either create a separate root helpers.ts
file from the root of your relevent api service to export and import your helper methods (functions) from, or place them somewhere in the index.ts
file of the relevant api service where it belongs to.
Finally, you may also want to consider writing another helper function that defines the expected JSON response schema. In our case, we should expect to encounter three different types of responses from our api service: success, fail, or error.
Defining Types
The next and final step is to ensure sure our fetch call functions are optimized for type-safety, since we want that make use of that sweet type support from our IDE!
So, for our “api-service” example, I’ve created a schema that will be used to parse the JSON response that we receive.
When we glue everything together from the previous section, we end up with a dependable fetch call function that we can confidently use in our app.
Bonus – Hooks!
Chances are that you use React since it’s the most popular front-end framework for developing web apps, and if you do, then undoubtedly you probably came across or heard of the @tanstack/react-query
library, which is a library used for handling server-side state management in React.
In that case, you may want to utilize some helper types and refactor your hooks as I've demonstrated in my examples.
Suggestions?
We’ve reached the end of this post, and so I ask you dear viewer – are they any feedback or improvements you would personally make to improve this approach?
If you found this post useful, then please do share this with anyone (like your socials) for who might find this useful.
I’ve got an X/Twitter account so send me a DM if you found out about me from here!
Appreciate the time you took out of your day to read this – goodbye!
Quick plug: I'm offering freelance web development and consulting services which you can contact me via t.uddin9121@gmail.com if your interested
Top comments (0)