Would you like to buy me a ☕️ instead?
Certain coding practices seem superfluous when you first encounter them, but sooner rather than later you get into a situation where you wish you had stuck with them. Wrapping third-party libraries instead of using them directly in your codebase is one of those practices.
Although I won’t advise you to wrap all of your dependencies 100% of the time, especially when it comes to libraries with a large API surface area, you should definitely consider it.
1:1 Re-Export
The 1:1 Re-Export variant is the most basic form of this pattern: we re-export axios
directly from our own http
util file.
// utils/http.js
export * from 'axios';
// services/user.js
import http from '../utils/http';
export const user = {
get({ id }) {
return http.get(`/user/${id}`);
},
// ...
};
Instead of scattering the axios
dependency all over our application, we only have one place where we are allowed to use this external dependency. This means that if the time comes when we have to replace it (because it is no longer maintained or we realize that fetch()
is good enough for us, for example), we can easily do this because we only have one file we need to update.
One problem with this approach is that if we rely heavily on more obscure/advanced implementation details of axios
(e.g., middleware, error handling), our code ends up pretty tightly coupled to this particular library anyway.
Reducing the Surface Area of Dependencies
Wrapping third-party dependencies opens up the opportunity to reduce the API surface area of external dependencies.
// utils/http.js
import axios from 'axios';
export default {
get: axios.get,
post: axios.post,
};
Here you can see that we can choose only to export the get
and post
methods from axios
. That way, we can nudge the developers of our project in the right direction by not making it possible to use axios.request
, for example.
Make It Your Own
We can even go a step further and make the API our own, so it matches our project’s code style.
// utils/http.js
import axios from 'axios';
export default {
get: ({ url, config }) => axios.get(url, config),
post: ({ url, data, config }) => axios.post(url, data, config),
};
In this example, we make the API of the get()
and post()
methods our own and use a single object instead of multiple parameters.
Downsides
One downside of this practice is that we lose the benefit of having an externalized documentation where we can point our developers to. In the case of the 1:1 re-export, we can tell developers to go to the original documentation.
// utils/http.js
// See https://github.com/axios/axios for docs.
export * from 'axios';
But you have to consider this if you change the API of the dependency. Though as long as the API is not too complicated, this should not be a big deal.
Wrapping It Up
As so often, also the practice of wrapping third-party dependencies is not without some downsides. But when it comes to dependencies that are likely to change or that are heavily used throughout your codebase, more often than not, the benefits outweigh the costs. Still you have to evaluate this on a case to case basis.