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
POST
request. - Grab the
POST
body fromoptions.body
- If the body is an instance of
FormData
, i.e. the request is a form submission withContent-Type
ofmultipart/form-data
, append thesecretCode
at the end. - If the payload is in
options.json
, we include oursecretCode
inside it.
- If the body is an instance of
- Return a new
Request
based off of the original request, with a only theoptions.body
modified: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! 💻