API Layer
Learn how to implement the API layer in large-scale applications to enable better API state management.
We'll cover the following
Implementing an API layer
Instead of having our components and services make API calls to the server directly, we want to create a layer in between, as shown below in the figure.
For this example, we’ll use the axios ibrary, one of the most popular promise-based clients for performing API calls. Still, the same approach applies to any HTTP method (xhr, fetch) or client (Firebase or Amplify). The API layer resides in the src/api
folder. We start with the base API file in which we configure an HTTP client instance and a few wrapper methods. An example of that is shown below.
//api/api.jsimport axios from 'axios';// Default config for the axios instanceconst axiosParams = {// Set different base URL based on the environmentbaseURL: process.env.NODE_ENV === 'development' ? 'http://localhost:8080' : '/',// You can also use an environmental variable// baseURL: process.env.VUE_APP_API_BASE_URL};// Create axios instance with default paramsconst axiosInstance = axios.create(axiosParams);// Main api functionconst api = (axios) => {// Wrapper functions around axiosreturn {get: (url, config) => axios.get(url, config),post: (url, body, config) => axios.post(url, body, config),patch: (url, body, config) => axios.patch(url, body, config),delete: (url, config) => axios.delete(url, config),};};// Initialise the api function and pass axiosInstance to itexport default api(axiosInstance);
We import the axios
package and create a new instance with default params (axiosParams
). This instance then passes to the api
function. The idea is that the object returned from the api
function is used in other API files. These don’t care about what kind of HTTP client or method is used, but rather utilizes exposed methods.
The main api.js
file is the first step of the API layer. The second step consists of creating feature-based API files. These files export
methods, which then can be imported and used anywhere in our application. To give an idea of what feature-based means, an app could have API files like authApi
, userApi
, productApi
, blogApi
, and so on. For demonstration purposes, here’s an animalApi.js
with two methods,one to fetch a random dog and one to fetch a random kitten:
// api/animalApi.jsimport api from './api';const URLS = {fetchDogUrl: 'breeds/image/random',fetchKittyUrl: 'images/search?format=json',};export const fetchDog = () => {return api.get(URLS.fetchDogUrl, {baseURL: 'https://dog.ceo/api/',});};export const fetchKitty = () => {return api.get(URLS.fetchKittyUrl, {baseURL: 'https://api.thecatapi.com/v1/',});};
Note that because both requests are made to a third-party server, we have to specify a baseURL
in the config object. However, we don’t have to do it for our own endpoints, as the baseURL
should be configured in the api.js
file.
The API methods defined in the animalApi.js
file can now be imported and used, as shown below. Make sure to add the AnimalApiExample
component in the routes config.
In the template, we display two images of a cat and a dog.
<!-- views/AnimalApiExample.vue --><template><div><div v-if="cat"><img class="animal-image" :src="cat" alt="cat" /></div><div v-if="dog"><img class="animal-image" :src="dog" alt="dog" /></div></div></template>
When the component is created, the fetchAnimals
method is called, and it initializes methods to get images for a dog and a cat. After receiving responses, both cat and dog image URLs are set on the instance.
What does the ?.
operator do on line 18? Here’s a hint:
Thus, we now have an API layer in place, and the flow is shown in the figure below. Instead of directly calling an HTTP method or a client, we use feature-specific API files to communicate with the server.
Let’s see how the functionality above is implemented in the Companion App.
In the Companion App, we have implementation.vue
in the views/api-layer/implementation
/ directory instead of AnimalApiExample.vue
.
Note: The code below may take a while to run. When the server starts, go to the app URL to see the output.
<template> <div id="app" class="bg-gray-100"> <GlobalSpinnerProvider> <Layout> <div id="nav"></div> <router-view /> </Layout> </GlobalSpinnerProvider> </div> </template> <script> import Layout from '@/layout/Layout' import GlobalSpinnerProvider from '@/components/common/spinner/GlobalSpinnerProvider.vue' import { setUser } from '@/services/stateful/userService' /** * Set user name for Managing State / Stateful Services example */ setUser({ name: 'William', }) export default { components: { Layout, GlobalSpinnerProvider, }, } </script> <style lang="scss" src="@/styles/index.scss"></style>
-A new Vue app that renders the App
component is created in the main.js
file. - Vuex store and Vue Router are installed on the Vue app. Base components are automatically registered and plugins are loaded.
- The
api.js
file contains the base API wrapper around the axios library. Other API files should use this API wrapper. - The
animalApi.js
file contains methods to fetch dog and cat data. - The
implementation.vue
component renders a card with a button to fetch animals and two images, one to display a picture of a dog and another of a cat.