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.

Press + to interact
//api/api.js
import axios from 'axios';
// Default config for the axios instance
const axiosParams = {
// Set different base URL based on the environment
baseURL: 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 params
const axiosInstance = axios.create(axiosParams);
// Main api function
const api = (axios) => {
// Wrapper functions around axios
return {
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 it
export 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:

Press + to interact
// api/animalApi.js
import 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 baseURLin 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>
Creating template for animal api example

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>
Implement an API layer in the application

-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.