Achieve Axios' Interceptor's Functionality with Ky
Background
axios is the most popular choice (based on my personal experience) when Vue/React developers need an HTTP client, and that’s also the case in a Vue 3 course that I have been following recently. However, I personally prefer the more platform-native (and hopefully more stable) Fetch API so I decided to go with my preference and use Fetch throughout the project. But if you’ve written plain Fetch code, you would know that it’s kinda verbose. Imagine writing plain XMLHttpRequest code without using axios. Luckily, we have ky to our rescue.
ky, just like axios, is an HTTP client library that we can use in the browser, key difference being it’s based on Fetch rather than XMLHttpRequest. There are also many other HTTP client libraries you can choose from the community and a comparison can be found at got’s repo.
What are we trying to achieve?
Using ky should be pretty straightforward if you follow its documentation. However, in the course I follow they have some addition security measures: In order to access a private API endpoint the instructor provides us, we need to:
- include our access code as a query string at the end of URL and
- append our access code to the payload if we are submitting a form with
FormData - include our access code in the payload if we are submitting JSON
Such global options can be configured in one axios/ky instance, which then gets exported and used all across your application. There is a well-known solution in axios: interceptors. Note that there are two types of interceptors, one for request and one for response. We only need to hook into the request interceptor here. The axios example would look like this:
| |
How are we going to do this in ky?
TL;DR;
| |
What did we do here? First, ky doesn’t have the concept of “interceptor”. Instead, it provides a set of hooks where we can tap into the lifecycle and make our changes to our requests and responses. More details can be found here. What we need to use is the beforeRequest hook.
Inside beforeRequest, the hook functions will receive two arguments: request of type Request and options, which is more or less of type RequestInit defined in TypeScript’s dom lib.
What we can do inside the hook function is described as such in ky’s doc:
The hook can return a Request to replace the outgoing request, or return a Response to completely avoid making an HTTP request.
Therefore, we are doing the following things with these two arguments:
- Determine the HTTP method. Only modify the payload if it’s a
POSTrequest. - Grab the
POSTbody fromoptions.body- If the body is an instance of
FormData, i.e. the request is a form submission withContent-Typeofmultipart/form-data, append thesecretCodeat the end. - If the payload is in
options.json, we include oursecretCodeinside it.
- If the body is an instance of
- Return a new
Requestbased off of the original request, with a only theoptions.bodymodified:1 2// basically a copy constructor return new Request(request, {body: <Your New Body>});
Update 2020-03-14:
The original proposed method was discovered to be problematic. The key thing here is for FormData to be correctly parsed, the request header Content-Type will need to include something called boundary (see the screenshot below). It’s used to separate different fields inside the FormData’s body.

When we create a new request with return new Request(request, {body: <Your New Body>}), we are reusing the Content-Type header and thus the outdated boundary value while a new boundary value gets generated for the updated FormData instance. Therefore, the boundary in the header and the one in the request body is out of sync. (I don’t know how and when the boundary gets generated and I’d be super happy if you can teach me about it! 😄) No matter what your backend is, the server will not be able to parse the form payload. This is also why when you use Fetch API, you would not want to set the Content-Type header yourself if you are submitting a FormData payload.
Some Extra Tricks
With the hooks, we can achieve some cool nifty tricks that would be cumbersome otherwise. One example would be to have a automatic loading effect while the request is pending.
Let’s say you have a pair of actions defined in you Vuex store named showLoading and hideLoading that toggle a global state property isShowingLoadingScreen which then toggles the visibility of your global loading screen mask. You can then do something like this:
| |
Here we also made use of the afterResponse hook.
NOTICE that we are putting the showLoading function at the beginning. Order matters! Or rather, the position of the hook function that returns a Request matters. As described in the doc:
An important consideration when returning a request or response from this hook is that any remaining beforeRequest hooks will be skipped, so you may want to only return them from the last hook.
Conclusion
There you have it! A nicely configured ky instance at your disposal. In this post we learned about how you can make use of the beforeRequest and afterResponse hooks to achieve similar functionalities as in axios’ interceptors. Check out ky’s doc to make use of its other features and configurations. A big shout out to Sindre for making such a nice library! 🎉 Give it a ⭐ if you enjoy it!
Next time I will share my experience on how to configure ky to include Authorization header for JWT authentication so stay tuned!
Thanks for reading and happy coding! 💻