How not to suffer with APIs

Rethinking Apicase and why I still never used axios

Anton Kosykh
ITNEXT

--

Typical OAuth

Click here to share this article on LinkedIn »

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:

Quite simple, I think

It’s ready to use. Let’s use it now

Simple requests with events-based API

So, the simplest request looks like this:

GET /api/posts and handle events of this request

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

Separated events for any use case — no more if (err instanceof Error)

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:

Fetch/XHR adapters use path-to-regexp to work with params. Also there is query option for query strings

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!

Now, you can reduce your store actions count (especially in Vuex). Because it’s API layer logic, not store

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.

before hook modifies request payload before request

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:

Are you solutions as reliable as this one?

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:

Line 26. doSingleRequest

Services have requests queue with currently running requests. And they also have some helpful methods:

  • doRequest — just start request and add it to queue
  • pushRequest — create a new request only after queue is clear
  • doSingleRequest — 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 same payload. 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:

Apicase queue explanation in less than 30 lines of code

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.
Debounced request example
Timeout example

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

--

--

Writer for

— Stricty pants nerd that sounds like total dork (according to one of the readers) — Also YOLO JavaScript Engineer