# Connecting the Frontend to the API Server | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)

**Author:** Ndianabasi Udonkang  
**Published:** 2021-11-09

In this lesson, we connected the frontend to the API server so that contacts are now fetched from the backend. We also configured CORS for extra security.

---

## Tags

- [JavaScript](/llms/technical-blog/tag/javascript.md)
- [Nodejs](/llms/technical-blog/tag/nodejs.md)
- [Vuejs](/llms/technical-blog/tag/vuejs.md)
- [Adonisjs](/llms/technical-blog/tag/adonisjs.md)
- [Quasar Framework](/llms/technical-blog/tag/quasar-framework.md)

## Part of Series: [Full-Stack Google Contacts Clone with Node.js (Adonisjs Framework) and Vue.js (Quasar Framework)](/llms/technical-blog/series/clnwu9bc991hiu3wo2vwmw45/full-stack-google-contacts-clone-with-node-js-adonisjs-framework-and-vue-js-quasar-framework.md)

This article is part of the **[Full-Stack Google Contacts Clone with Node.js (Adonisjs Framework) and Vue.js (Quasar Framework)](/llms/technical-blog/series/clnwu9bc991hiu3wo2vwmw45/full-stack-google-contacts-clone-with-node-js-adonisjs-framework-and-vue-js-quasar-framework.md)** series.

### Articles in this Series:

- [Introduction to Full-Stack Google Contacts Clone with Node.js (Adonisjs Framework) and Vuejs (Quasar Framework)](/llms/technical-blog/article/xe5mlxg43s9rpx1pxsvfdp3r/introduction-to-full-stack-google-contacts-clone-with-node-js-adonisjs-framework-and-vuejs-quasar-framework.md)
- [Workspace Setup | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/fo8str8yjpkz5dygxg875bpw/workspace-setup-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Frontend Overview | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/nxgmng6fhf1cwjk30coflro2/frontend-overview-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [The Left Drawer | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/m6fagvy2il5h65llxxbbl9os/the-left-drawer-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Improve The Header | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/jjwbxcg450jmkkcrrmkmwvwj/improve-the-header-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [New Contact Form Design | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/ebguwpkntk3empakunrki4dq/new-contact-form-design-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Validating the Contact Form with Vuelidate | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/z0xr9m2ljler2cwnpsw2ycwa/validating-the-contact-form-with-vuelidate-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Designing the Contacts Table | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/h2p1x1z406ib38zu2dci3jur/designing-the-contacts-table-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Designing the Contacts Table (Part 2) | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/j0iscua9gg8ekuz8ifh6chpn/designing-the-contacts-table-part-2-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Refactoring the Main Layout | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/uktmlqsivwtr3cvnpld59k9q/refactoring-the-main-layout-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Improving User Experience with the Contacts Table | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/dzs812auf61ej7qu2cg726dh/improving-user-experience-with-the-contacts-table-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Designing the Contact Details Page | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/kf9pz97n63fvgfrcy5viwzrg/designing-the-contact-details-page-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [Creating the Contact Edit Page | Full-Stack Google Contacts Clone with Adonis.js/Node.js and Quasar (Vue.js)](/llms/technical-blog/article/fmr90prj84c9zn0633jc368g/creating-the-contact-edit-page-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-vue-js.md)
- [How Software Backends Work | Full-Stack Google Contacts Clone with AdonisJs (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/aca8fcrghz70nn1wqg3uzbum/how-software-backends-work-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Setting Up The Backend | Full-Stack Google Contacts Clone with AdonisJs (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/fxb49fmz9ljbwrimbnnzudfv/setting-up-the-backend-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Why Choose the AdonisJs Framework? | Full-Stack Google Contacts Clone with AdonisJs (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/lnhm5oudx34yo2869gmcms37/why-choose-the-adonis-js-framework-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Setting Up Our API Server with AdonisJs Framework | Full-Stack Google Contacts Clone with AdonisJs (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/x1jgmet84aqj9qggu5mulkh0/setting-up-our-api-server-with-adonis-js-framework-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Setting Up Postman with the API Server | Full-Stack Google Contacts Clone with AdonisJS (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/v4qehksislosmuy92qgjr4ct/setting-up-postman-with-the-api-server-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [The Model-View-Controller Design Pattern in AdonisJS | Full-Stack Google Contacts Clone with AdonisJS (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/h5wqql65iejop5xrplujw458/the-model-view-controller-design-pattern-in-adonis-js-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Create Column Definitions & Insert New Contacts | Full-Stack Google Contacts Clone with AdonisJS (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/asc2emsho4u73r6jb2gxjddw/create-column-definitions-and-insert-new-contacts-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Data Validation & Sanitisation with AdonisJS | Full-Stack Google Contacts Clone with AdonisJS (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/bcey0ac7m5sbs9hqy1h824sl/data-validation-and-sanitisation-with-adonis-js-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Creating a Middleware and Updating a Contact | Full-Stack Google Contacts Clone with AdonisJS (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/oqxzmtgfy1r3oewpnedc7wf6/creating-a-middleware-and-updating-a-contact-full-stack-google-contacts-clone-with-adonis-js-node-js-and-quasar-framework-vue-js.md)
- [Fetching and Deleting a Contact with AdonisJS | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/p6bh0ponq43u3vi6o8uww84x/fetching-and-deleting-a-contact-with-adonis-js-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- [Using Model Factories and Seeders in AdonisJS | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/cfotveuxbgjtdp12l6pzcb2y/using-model-factories-and-seeders-in-adonis-js-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- [Creating and Registering a Vuex Module | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/cw96ewnzujgsz2e2garhbuzb/creating-and-registering-a-vuex-module-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- [Connecting UI Components to the Vuex Store | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/fdyqp17yehbnqrjvjvab4b5b/connecting-ui-components-to-the-vuex-store-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- **Connecting the Frontend to the API Server | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)** (Current Article)
- [Creating and Updating Contacts From the Frontend | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)](/llms/technical-blog/article/t15w2s3ao9jo74l96stjo7z6/creating-and-updating-contacts-from-the-frontend-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js.md)
- [Uploading Files and Creating Avatars for Contacts With AdonisJS and Axios](/llms/technical-blog/article/hg9ilaubqesm8angg0rhrjp9/uploading-files-and-creating-avatars-for-contacts-with-adonis-js-and-axios.md)
- [Deleting a Contact with AdonisJS and Vuejs](/llms/technical-blog/article/zdr4pxmgcax2dsfnwimhty0g/deleting-a-contact-with-adonis-js-and-vuejs.md)

---

## Article Content

Up till now, there is no link between the user interface of our Google Contacts Clone app and the API server. When we developed the API server, we used Postman to test the route. Now, we are ready to connect both worlds. In this lesson, we will introduce the `axios` HTTP request library and use it send requests to the API server. We will no longer use the mock data on the frontend. After connecting to the API server, we will make use of the mock data created with the `Contact` factory and seeded to the database. We will also configure Cross-Origin Resource Sharing (CORS) on our API server to restrict access to the API server.

Let's start by creating a new branch for our repo:

```bash
# Make sure you are within your project
git checkout -b 18-connect-the-frontend-to-the-api-server
```

## Pre-requisites

Before we go into the major discussions, we need to install the `dotenv` module and add it to the `quasar.conf.js` file. The `dotenv` module makes environment variables defined in the `.env` file on the frontend to be available within the JavaScript code after the frontend is built. Why are we doing this? It is important to understand that the `.env` file used for storing environment variables on the frontend is not directly read like other JavaScript files - as a matter of fact, the file won't be copied to the `dist` directory when you build the frontend. In order to make use of those variables, they have to be compiled and injected into the JavaScript files so that they are available for use during the runtime. Typically, they are injected into the global `process.env` object. So if you have the following variables in the `.env` file:

```text
NODE_ENV=development
API_HOST=127.0.0.1
API_PORT=3333
```

They will be accessible as: `process.env.NODE_ENV`, `process.env.API_HOST`, and `process.env.API_PORT` after they are injected. The `dotenv` NPM module makes this possible.

> In most cases, you won't be able to destructure the `process.env` object. So don't do this: `const {NODE_ENV, API_HOST, API_PORT} = process.env`. Always access each variable through the full property path like `process.env.NODE_ENV`.

Install the `dotenv` module:

```bash
cd ui
yarn add -D dotenv
```

Open the `ui/quasar.conf.js` file. Refer to [this snapshot](https://github.com/ndianabasi/google-contacts/blob/19-connect-ui-to-API-server/ui/quasar.conf.js#L53). Within the `build` property, add the following line:

```diff
build: {
+   env: require("dotenv").config().parsed,
  vueRouterMode: "hash", // available values: 'hash', 'history'
...
}
```

Let's all enable extra build options in the `quasar.conf.js` file:

```diff
build: {
    ...
+    preloadChunks: true,
+    showProgress: false,
+    gzip: true,
+    analyze: true,
    ...
}
```

Open the `ui/.env` file and add the following:

```text
NODE_ENV=development
API_HOST=127.0.0.1
API_PORT=3333
```

## Preparing the API Server

We will make few modifications to the API server before we face the modifications in the frontend. First, we ensure that the keys of the contact objects sent from the backend after serialisation are consistent with the types on the frontend. Let me clarify.

By default, AdonisJS will return the keys of your models in `snake_case`. These also match the names of columns on our database. For example: property `firstName` in our `Contact` model is stored as `first_name` on the `contacts` table. AdonisJS automatically does the case conversion when fetching or storing data via the default [NamingStrategy](https://docs.adonisjs.com/reference/orm/naming-strategy). You should dedicate some time to study this. So, on the model file, the properties are defined in `camelCase` while on the database, the corresponding columns are defined in `snake_case` as shown below:

```ts
// App/Models/Contact
// Part of the column definitions for the `Contact` model on the backend
export default class Contact extends BaseModel {
...
   @column()
   public firstName: string // <-- Camel Case

   @column()
   public surname: string

   @column()
   public company?: string | null | undefined

   @column()
   public jobTitle?: string | null | undefined // <-- Camel Case
...
}

```

Meanwhile, by default, AdonisJS will return the properties in `snake_case` as defined on the database tables. The JSON below shows the default way the keys are returned after serialization.

> Serialization is the process of converting objects into JSON format before data is sent to the client/backend. During serialisation, you can modify the way the keys and values each JSON entry are displayed.

```json
{
    "id": "ckuyjdrqt002jwsvo465meq52",
    "first_name": "Angel",
    "surname": "Runolfsson",
    "company": "Jenkins - Muller",
    "job_title": null,
    "email1": "Angel65@hotmail.com",
    "email2": "Angel.Runolfsson@kory.com",
    "phone_number1": "955.249.9131 x63764",
    "phone_number2": "656.521.8253",
    "country": "Senegal",
    ...
    "created_at": "2021-10-19T21:25:57.000+01:00",
    "updated_at": "2021-10-19T21:25:57.000+01:00"
}
```

If we get the contact data in `snake_case`, it will lead to type mismatch on the frontend because the properties of `Contact` type are defined in `camelCase` as shown below:

```ts
// ui/src/types/index.ts
// The type definition for `Contact` on the frontend
interface Contact
  extends Record<string, string | null | undefined | number> {
  id: string;
  firstName: string;
  surname: string;
  company?: string | null | undefined;
  jobTitle?: string | null | undefined;
  email1: string;
  email2?: string | null | undefined;
  phoneNumber1: string;
  phoneNumber2?: string | null | undefined;
  country?: string | null | undefined;
  streetAddressLine1?: string | null | undefined;
  streetAddressLine2?: string | null | undefined;
  city?: string | null | undefined;
  state?: string | null | undefined;
  birthday?: string | null | undefined;
  website?: string | null | undefined;
  notes?: string | null | undefined;
}
```

So, how do we resolve this conflict? Gladly, AdonisJS can handle this gracefully. We only need to inform AdonisJS of how we want teach column property in the `Contact` model to be serialised. So open `api/app/Models/Contact.ts` and make the following changes. Refer to the snapshot for [the updated](https://github.com/ndianabasi/google-contacts/blob/19-connect-ui-to-API-server/api/app/Models/Contact.ts) file.

```diff
-  @column()
+  @column({ serializeAs: 'firstName' })
  public firstName: string

-  @column()
+  @column({ serializeAs: 'jobTitle' })
  public jobTitle?: string | null | undefined

-  @column()
+  @column({ serializeAs: 'phoneNumber1' })
  public phoneNumber1: string

-  @column()
+  @column({ serializeAs: 'phoneNumber2' })
  public phoneNumber2?: string | null | undefined

-  @column()
+  @column({ serializeAs: 'streetAddressLine1' })
  public streetAddressLine1?: string | null | undefined

-  @column()
+  @column({ serializeAs: 'streetAddressLine2' })
  public streetAddressLine2?: string | null | undefined

-  @column()
+  @column({ serializeAs: 'postCode' })
  public postCode?: string | null | undefined

-  @column.dateTime({ autoCreate: true })
+  @column.dateTime({ autoCreate: true, serializeAs: 'createdAt' })
  public createdAt: DateTime

-  @column.dateTime({ autoCreate: true, autoUpdate: true })
+  @column.dateTime({ autoCreate: true, autoUpdate: true, serializeAs: 'updatedAt' })
  public updatedAt: DateTime
```

As seen in the above diff, we are adding the property `serializeAs` to each column property which we need to serialise in `camelCase`. When you do this, the JSON data will be serialised in `camelCase` to match the types on the frontend.

```json
{
    "id": "ckuyjdrqt002jwsvo465meq52",
    "firstName": "Angel",
    "surname": "Runolfsson",
    "company": "Jenkins - Muller",
    "jobTitle": null,
    "email1": "Angel65@hotmail.com",
    "email2": "Angel.Runolfsson@kory.com",
    "phoneNumber1": "955.249.9131 x63764",
    "phoneNumber2": "656.521.8253",
    "country": "Senegal",
    ...
    "createdAt": "2021-10-19T21:25:57.000+01:00",
    "updatedAt": "2021-10-19T21:25:57.000+01:00"
}
```

## Configure Crosss-Origin Resource Sharing (CORS)

Since we are connecting to the API server from the frontend for the first time, we need to enable CORS (Cross-Origin Resource Sharing) on the API. But why do we need to enable CORS? CORS provides a layer of security for your backend by restricting clients which can connect to it. Imagine that we finally host our Google Contacts Clone app. If we choose to host the frontend at `https://google-contacts-clone.app` and the API server at `https://google-contacts-clone.app/api`, both apps (frontend and API) are on the same origin - `google-contacts-clone.app`. Since they are on the same origin, CORS won't have any effect. The browser will send the request with the confidence that the browser is recognised by the API server automatically because they are on teh same origin. However, we might need decide to host both apps on different origins, for example, the frontend at `https://google-contacts-clone.app` and API server at `https://api.google-contacts-clone.app`, the browser will enforce CORS restrictions and won't send the API request. Why? Because the browser does not trust that the frontend hosted at `https://google-contacts-clone.app` is authorised to access the API server at `https://api.google-contacts-clone.app`.

To solve this, AdonisJS has a CORS module which we can activated and configure the origins the API server should recognise. Open `api/config/cors.ts`. Refer to [this snapshot](https://github.com/ndianabasi/google-contacts/blob/19-connect-ui-to-API-server/api/config/cors.ts) for the updated file. Make the following changes:

```diff
-  enabled: false,
+  enabled: true,
    ...
-  origin: true,
+  origin: ['http://localhost:8008', 'http://127.0.0.1:8008'],
```

Above, we have enabled the CORS module and set the API server to only receive requests from either `http://localhost:8008` or  `http://127.0.0.1:8008`.

Why is this a security feature? It is possible for a malicious actor to attempt to send API requests from a different origin, maybe by cloning your app or embedding your app within another app/website. The browser will block such unauthorised cross-origin requests.

> Note that CORS is a browser feature only. Someone can still send request from a terminal or an API tool such as Postman without being restricted by CORS. This is because requests sent from a browser have an origin whereas a terminal or Postman has no origin. This is the reason you must adequately hardened your API server.

We are done with the few changes on the API server. Save the files.

## Improve the Frontend Types

Open the file `ui/src/types/index.ts`. Refer to [this snapshot](https://github.com/ndianabasi/google-contacts/blob/19-connect-ui-to-API-server/ui/src/types/index.ts) for the updated file. Copy-and-paste all the content from the snapshot to the `ui/src/types/index.ts` file.

We add the following interfaces:

```ts
export type PaginatedData = {
  data?: Record<string, unknown>;
  meta: {
    current_page: number;
    first_page: number;
    first_page_url: string;
    last_page: number;
    last_page_url: string;
    next_page_url: string;
    per_page: number;
    previous_page_url: string;
    total: number;
  };
};

export interface ResponseData {
  message?: string;
  status?: number;
  statusText?: string;
  stack?: string;
  data: Record<string, unknown>;
  errors?: Array<{ rule: string; field: string; message: string }>;
}

export interface HttpResponse extends AxiosResponse {
  data: ResponseData & string;
  message?: string;
  code?: string;
  stack?: string;
  headers: Record<string, string>;
}

export interface HttpError extends AxiosError {
  response?: HttpResponse;
}

export interface PaginatedContact {
  id: string;
  first_name: string;
  surname: string;
  email1: string;
  phone_number1: string;
  company: string;
  job_title: string;
}
```

## Improve the Store Index

Open the file `ui/src/store/index.ts`. Refer to [this snapshot](https://github.com/ndianabasi/google-contacts/blob/19-connect-ui-to-API-server/ui/src/store/index.ts) for the updated file. Copy-and-paste all the content from the snapshot to the `ui/src/store/index.ts` file.

We are going to improve the store index file by adding more properties to the object returned from `state()` function and also defining `getters` to retrieve those state properties for us.

From Lines 23 to 32, we update the `stateInterface` and add the `RootState` interface.

```diff
export interface StateInterface {
-  // Define your own store structure, using submodules if needed
-  contacts: ContactStateInterface;
+  contacts?: ContactStateInterface;
+}
+
+ export interface RootState {
+   httpTimeout: number;
+   apiPort: string;
+   apiVersion: string | null;
+   apiProtocol: string;
+   apiHost: string;
}
```

From Line 48 to 76, we update the `state()` function within the `store()` function to:

```ts
    state() {
      return {
        apiHost: process.env.API_HOST ?? "127.0.0.1",
        apiPort: process.env.API_PORT ?? "3333",
        apiVersion: null,
        apiProtocol:
          window.location.hostname === "localhost"
            ? "http://"
            : ["staging", "production"].includes(process.env.NODE_ENV)
            ? "https://"
            : "http://",
        httpTimeout: process.env.NODE_ENV === "production" ? 60000 : 30000,
      };
    },

    getters: {
      getHttpProtocol: (state) => state.apiProtocol,
      getRootURL: (state) =>
        `${state.apiProtocol}${state.apiHost}:${state.apiPort}`,
      getBaseURL: (state) =>
        `${state.apiProtocol}${state.apiHost}:${state.apiPort}${
          state.apiVersion ? `/${state.apiVersion}` : ""
        }`,
      getHttpTimeout: (state) => state.httpTimeout,
      getHttpNoAuthOptions: (state, getters) => ({
        baseURL: getters.getBaseURL as string,
        timeout: getters.getHttpTimeout as string,
      }),
    },
```

## Improve the Axios Instance

Open the file `ui/src/boot/axios.ts`. Refer to [this snapshot](https://github.com/ndianabasi/google-contacts/blob/19-connect-ui-to-API-server/ui/src/boot/axios.ts) for the updated file. Copy-and-paste all the content from the snapshot to the `ui/src/boot/axios.ts` file.

What happened in the `ui/src/boot/axios.ts` file:

At Line 1 and 2, we disable some eslint rules due to type issue with Vuex methods.

At Lines 5 and 6, we imported `Notify` and `HttpError` from `quasar` and `src/types`, respectively.

```ts
import { Notify } from "quasar";
import { HttpError } from "src/types";
```

At Line 14, we remove the options with `baseURL` while creating the `axios` instance. Why? We want to get the `baseURL` and `timeout` from the Vuex store and the Quasar provides the store in the context of the `boot` function. So we have to set the `baseURL` and timeout`within the`boot\` function.

```diff
- const api = axios.create({ baseURL: "https://api.example.com" });
+ const api = axios.create();
```

From Lines 16 to 18, we destructure `store` from the context of the boot function and set the default `baseURL` and `timeout`.

```diff
- export default boot(({ app }) => {
-  // for use inside Vue files (Options API) through this.$axios and this.$api
+ export default boot(({ app, store }) => {
+  api.defaults.baseURL = store.getters.getHttpNoAuthOptions.baseURL;
+  api.defaults.timeout = store.getters.getHttpNoAuthOptions.timeout;
```

From Lines 22 down, we define an `axios` response interceptor for the purpose of handling errors centrally.

```ts
api.interceptors.response.use(
    (response) => response,
    async (error: HttpError) => {
      console.log(error.response);
      if (error?.response?.status === 400) {
        console.log(error.response.data);

        Notify.create({
          message:
            error?.response?.data?.message ??
            (typeof error?.response?.data === "string"
              ? error?.response?.data
              : "You made a bad request."),
          type: "negative",
          position: "top",
          progress: true,
          timeout: 5000,
          actions: [
            {
              label: "Dismiss",
              color: "white",
            },
          ],
        });
      } else if (error?.response?.status === 403) {
        Notify.create({
          message:
            "You are not permitted to perform the requested action. Make sure that you are viewing the right company.",
          type: "negative",
          position: "top",
          progress: true,
          timeout: 5000,
          actions: [
            {
              label: "Dismiss",
              color: "white",
            },
          ],
        });
      } else if (error?.response?.status === 422) {
        // Intercept validation errors
        const validationErrors = error?.response?.data?.errors;
        if (Array.isArray(validationErrors) && validationErrors.length) {
          const errorListItems: string[] = validationErrors.map(
            (err) => `<li>${err.message}</li>`
          );
          Notify.create({
            message: `<ul>${errorListItems.join("")}</ul>`,
            html: true,
            type: "negative",
            position: "top",
            progress: true,
            timeout: 10000,
            actions: [
              {
                label: "Dismiss",
                color: "white",
              },
            ],
          });
        }
      } else if (error?.response?.status === 404) {
        Notify.create({
          message:
            error?.response?.data?.message ??
            (error?.response?.data as string) ??
            "Request resource was not found!",
          type: "negative",
          position: "top",
          progress: true,
          timeout: 5000,
          actions: [
            {
              label: "Dismiss",
              color: "white",
            },
          ],
        });
      } else if (
        error &&
        error.response &&
        error.response.status &&
        error.response.status >= 500
      ) {
        Notify.create({
          message:
            error?.response?.data?.message ??
            (error?.response?.data as string) ??
            "Internal Server Error",
          type: "negative",
          position: "top",
          progress: true,
          timeout: 5000,
          actions: [
            {
              label: "Dismiss",
              color: "white",
            },
          ],
        });
      }

      return Promise.reject(error);
    }
  );
```

## Introduce API Requests in the `actions` of the Vuex `contacts` Module

Open the file `ui/src/store/contacts/actions.ts`. Refer to [this snapshot](https://github.com/ndianabasi/google-contacts/blob/19-connect-ui-to-API-server/ui/src/store/contacts/actions.ts) for the updated file. Copy-and-paste all the content from the snapshot to the `ui/src/store/contacts/actions.ts` file.

What changes happened?

From Lines 1 to 2, we relax some eslint rules for obvious reasons. Then, from Lines 3 to 9, we import extra types from `src/types`.

At Line 13, we import the `axios` instance which was exported as `api` from `boot/axios.ts`.

Now, from Line 17, for the `LOAD_CURRENT_CONTACT` module action, we will substitute the logic of fetching data from local mock contacts source (`data/Google_Contacts_Clone_Mock_Data`) with a real API request.

```diff
const actions: ActionTree<ContactStateInterface, StateInterface> = {
  LOAD_CURRENT_CONTACT({ commit }, id: Contact["id"]): Promise<Contact> {
-    return new Promise((resolve, reject) => {
-      try {
-        const currentContact = rawContacts
-          .filter((contact) => contact.id === id)
-          .reduce((prev, cur) => {
-            prev = { ...cur };
-            return prev;
-          }, {} as Contact);
+    return new Promise(async (resolve, reject) => {
+      await api
+        .get(`/contacts/${id}`)
+        .then((response: HttpResponse) => {
+          const currentContact = response.data.data as Contact;
+          commit("setCurrentContact", currentContact);

-        commit("setCurrentContact", currentContact);
-
-        return resolve(currentContact);
-      } catch (error) {
-        return reject(error);
-      }
+          return resolve(currentContact);
+        })
+        .catch((error) => reject(error));
    });
  },
```

At Line 17, we make the promise callback `async`.

From Lines 18 to 22, we introduce the axios instance `api` and call the `get` method with the path `/contacts/${id}`. When the response is returned, at Line 22, we commit the `setCurrentContact` mutation with the `currentContact` as payload. We also resolve the `currentContact` and make it accessible to the initiating function which is the action dispatched in either the `Contact View` or `Contact Edit` views. At Line 26, we also reject any `error` and make the `error` accessible to the initiating function.

Similarly, for the `LOAD_CONTACTS` module action, we introduce the API call `GET /contacts`.

```diff
  LOAD_CONTACTS(
    { commit },
    { nextPage, pageSize }: { nextPage: number; pageSize: number }
-  ): Promise<Contact[]> {
-    return new Promise((resolve, reject) => {
-      try {
-        const requestedContacts = [...rawContacts].slice(
-          nextPage <= 1 ? 0 : (nextPage - 1) * pageSize,
-          nextPage <= 1 ? pageSize : nextPage * pageSize
-        );
+  ): Promise<ResponseData["data"]> {
+    return new Promise(async (resolve, reject) => {
+      await api
+        .get("/contacts", { params: { page: nextPage, perPage: pageSize } })
+        .then((response: HttpResponse) => {
+          const paginatedContacts = response.data.data
+            .data as PaginatedContact[];
+          const paginatedMeta = response.data.data
+            .meta as PaginatedData["meta"];

-        commit("setContactList", requestedContacts);
-        commit("setTotalContacts", rawContacts.length);
+        commit("setContactList", paginatedContacts);
+        commit("setTotalContacts", paginatedMeta.total);

-        return resolve(requestedContacts);
-      } catch (error) {
-        return reject(error);
-      }
+          return resolve(response.data.data);
+        })
+        .catch((error) => reject(error));
    });
  },
};
```

## Minor Changes to Contacts Table

Open the file `ui/src/pages/Index.vue`. Refer to [this snapshot](https://github.com/ndianabasi/google-contacts/blob/19-connect-ui-to-API-server/ui/src/pages/Index.vue) for the updated file. Copy-and-paste all the content from the snapshot to the `ui/src/pages/Index.vue` file.

The following changes were made:

At Line 139, import the `PaginatedData` interface from `types/index.ts`.

From Lines 157 to 160, we modify the `totalContacts` computed property to allow setting of value. This demonstrates another syntax for creating computed properties in Vue 3.

```diff
-    const totalContacts = computed(
-      () => store.getters["contacts/totalContacts"] as number
-    );
+    const totalContacts = computed({
+      get: () => store.getters["contacts/totalContacts"] as number,
+      set: (value) => value,
+    });
```

The `get` method does the work of the familiar syntax of the computed property. While the `set` method allows explicitly setting the value from anywhere.

At Line 168, we get the resolved `data` from our action which contains the `PaginatedData`. At Line 172, we set the `totalContacts` computed property with the value `data.meta.total`. Line 172 triggers the `set` method in the computed property.

```diff
    const stopContactListEffect = watchEffect(async () => {
      await store
        .dispatch("contacts/LOAD_CONTACTS", {
          nextPage: nextPage.value,
          pageSize,
        })
-        .then(() => {
+        .then((data: PaginatedData) => {
          void nextTick(() => {
            tableRef.value?.refresh();
            loading.value = false;
+            totalContacts.value = data.meta.total;
          });
        });
    });
```

At Line 177, we convert the `lastPage` constant to a computed property.

At Lines 181 and 182, we simplify the strict equality and update `lastPage` to `lastPage.value`

```diff
-        loading.value !== true &&
-        nextPage.value < lastPage &&
+        loading.value === false &&
+        nextPage.value < lastPage.value &&
```

## Minor Changes to the `CreateContact.vue`

Open the file `ui/src/pages/contacts/CreateContact.vue`. Refer to [this snapshot](https://github.com/ndianabasi/google-contacts/blob/19-connect-ui-to-API-server/ui/src/pages/contacts/CreateContact.vue) for the updated file. Copy-and-paste all the content from the snapshot to the `ui/src/pages/contacts/CreateContact.vue` file.

Remove the Line `import { contacts } from "../../data/Google_Contacts_Clone_Mock_Data";`

At Line 286, make the following changes:

```diff
-       if (key !== "id") {
+      if (["id", "createdAt", "updatedAt"].includes(key) === false) {
```

Those are all the changes for the branch. Save all files, serve both the frontend and API server. Open the homepage and view a contact. The contacts should be fetched from the API server now. Press CTRL+Shift+I to open the devtools and switch to the Network tab to monitor the requests and responses.

```bash
# Serve the frontend
cd ui
yarn serve

# Split the terminal and serve the backend
cd api
yarn serve
```

```bash
git add .
git commit -m "feat(api): complete connection of frontend to the API server"
git push origin 18-connect-the-frontend-to-the-api-server
git checkout master
git merge master 18-connect-the-frontend-to-the-api-server
git push origin master
```


*This document was generated from the live article page on https://ndianabasi.com/technical-blog/article/fk4262359gg3g7f0jn95fn9b/connecting-the-frontend-to-the-api-server-full-stack-google-contacts-clone-with-adonis-js-framework-node-js-and-quasar-framework-vue-js • 2026-06-07*
