How not to suffer with APIs
Rethinking Apicase and why I still never used axios
Everyday I see the questions “How to work with API?”, “Can you give some best practices”, “How to cook axios” in frontend chats. They get a lot of answers but all of them can be reduced to three types
– Fetch is quite enough
Fatal mistake. Tons of boilerplate code guaranteed, I have nothing more to say
– Axios is good and recommended by community
Some people just use axios and don’t care about. In my opinion, it looks good only on “Hello world” examples. When we want to go deeper, we need to write own wrappers, handlers etc. And here come the above questions.
– I wrote my wrapper, it’s not the best but enough for my cases
For your cases. Some people share their wrapper functions or even classes but it still looks ugly. Often it’s just axios calls wrapped into a function that checks auth, handle response etc. It’s very specific, it covers only their use cases, it can’t be used in another apps.
It would seem, so much time has passed, we have really cool frameworks to work with view, we have really cool libraries for state management, but…
We still haven’t unified tool for comfortable work with API
Introducing Apicase
Apicase is a powerful library that moves the most of work with API to a separated layer. It’s based on 4 principles
- events-based requests handling
- middlewares to update/change-on-fly/undo/redo API calls
- services with unlimited inheritance
- adapters instead of concrete tools (fetch/xhr)
So, I don’t want to deep dive into theory and lyrics, let’s deep dive and try to write some requests.
Getting started
Installation
Install it with your favourite package manager (npm, yarn, pnpm, or a horse in a coat)
npm install @apicase/core @apicase/adapter-fetch
I usually use fetch, but if you want xhr, you can install @apicase/adapter-xhr
instead. Don’t worry, both have the same API.
Then, we need to wrap our adapter into apicase
:
It’s ready to use. Let’s use it now
Simple requests with events-based API
So, the simplest request looks like this:
Why not just .then()
and .catch()
?
Apicase follows “business logic failures are not exception” principle. It means that you need at least 3 states of your request. Promises have only 2 — success or fail. Events allows you to handle as much things as you want. Like
You still can use async/await
, of course, I saved this for you, my little syntax sugar lovers:
Note: promise will be rejected only when error
happens. Otherwise, it will be positively resolved.
Simple requests with apicase
look similar to axios requests:
Is that all? Of course, not.
“Create a post” example
Apicase services
So, as I said before, axios is not enough when we go deeper. It just makes us to write a lot of boilerplate code or wrap it into functions.
So, we don’t want to write part of request data that is repeated (in previous example: url
, method
, headers
). Let’s create a service!
Also, services may be extended as much as you want, so you can do something like this:
I bet it already has more interesting ways to work than your way to organize APIs. But it’s not all.
So, we reduced some boilerplate code here. But here we have our token
passed statically. It will never change. So, hooks are here to solve your problem.
Apicase hooks
Hooks intercept request in some moment and may modify request payload, response, cancel request or retry it, do another requests etc. Like axios interceptors, but much better.
Looks easy — we just add before
hook that adds token
to headers and then goes to the next step.
Can we do better? Of course! We can move it to our root service and then just inherit it.
What if we don’t need to pass auth token everywhere? Apicase has meta
options that is passed to hooks. Like vue-router
has, if you know :)
So, here GetProfile
service won’t have token passed.
Anything else? Of course, because I can do it!
Going hardcore
“Refresh token” is no longer painful
So, let’s imagine that we have API that requires auth token. And if we receive 401 error, we can try to refresh our token within specific route. Typical oAuth.
We can do it without any boilerplate code only once using hooks.
Because hooks are asynchronous, we can do another requests there. And we can also change request state from fail to success and vice versa. See this:
So, if request fails with 401 status, we’ll try to refresh token. If it’s OK, request starts again with no additional actions and handling in application.
Even harder! 2 requests are simultaneous failed
Oh yeah! You have two simultaneous API requests and both of them are failed. Both of them will try to refresh token, but we need only one there.
Rewrite previous code? Create a bycicle? Lol, I can fix it using only one word. Don’t believe? Just see:
Services have requests queue with currently running requests. And they also have some helpful methods:
doRequest
— just start request and add it to queuepushRequest
— create a new request only after queue is cleardoSingleRequest
— create a new request only if there are no currently running requests. Otherwise, return this currently running request.doUniqueRequest
— create a new request only if there are no currently running requests with the samepayload
. Otherwise, return this currently running request.
You just create request or listen to currently running one. Isn’t that beautiful?
Finish? NO!
Requests cancellation
No more shitty cancellation tokens, just .cancel()
it
XHR adapter uses xhr.abort()
to cancel requests
Fetch adapter uses new AbortController
(if available)
Request queues
Wanna make a synchronous queue of requests? Do it, bro:
Delayed/time limited/debounced requests
Requests has options property with some coll things:
- immediate: set to
false
and request won’t start automatically. You have to use.start()
to start it manually - delay: just delay. Request will start after N ms (works for automatically too)
- debounce: classic debounce. Useful for autocomplete from server — create request, then
.start()
it with N ms delay. If it’s called before time’s up — restart timer again - timeout: if you want to add time limit for request, you can use this option.
Services tree plugin
Services creation may look gigantic and monstrous. I made a little tool that allows to declare services using one JSON object:
You can pass hooks, meta, options as well. In addition, there is on
property to pass events:
And, if you want quite clean structure, you can use rest
and restWrapped
helpers:
Roadmap
It was so long way to do so huge work. I tried to do it from scratch at least 4 times. Now it looks very strong (at least, for me). My future plans for Apicase:
- Normalize and standardize API: I think some things aren’t strict enough. Also I could skip some bugs. Before 1.0 release, I will work on it.
- Create devtools: the most important feature — plugin for Chrome with requests log, services visualization etc
- Types for TS/Flow: there’s a sad moment — Apicase probably won’t have typings for responses because of hooks. But typings for requests may be created well.
- More tools to simplify work: I also want to create more helper tools (like services) to make work with API even more comfortable.
Links
Apicase documentation: click here (it’s still in progress)
Github repos: core, adapter-fetch, adapter-xhr, services
My boring twitter: follow me