Creating and Updating Contacts From the Frontend | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)

Creating and Updating Contacts From the Frontend | Full-Stack Google Contacts Clone with AdonisJS Framework (Node.js) and Quasar Framework (Vue.js)

In this lesson, we will make few changes to the frontend and API server so that we can create and update contacts from the frontend of the Google Contacts Clone app.

Let's create a new branch of our project:

# Make sure you are within your project
git checkout -b 19-create-and-update-contacts-from-the-frontend

Pre-requisites

Let's update dependencies in our frontend (that is, the Quasar Framework).

# Change into the `ui` directory
cd ui

# check for upgradable packages
quasar upgrade
# if you get the error: `Command not found`, 
# access the `quasar` command directly
./node_modules/.bin/quasar upgrade

# do the actual upgrade
quasar upgrade --install
# if you get the error: `Command not found`, 
# access the `quasar` command directly
./node_modules/.bin/quasar upgrade --install

Improving the ContactsController

Open the api/app/Controllers/Http/ContactsController.ts file. Refer to this snapshot of the file. Copy-and-paste the content of the snapshot into the api/app/Controllers/Http/ContactsController.ts file.

We will make the following changes:

At Line 13, we add the orderBy query method so that we can sort the paginated results by first_name in ascending order.

      const contacts = await Contact.query()
        .select(['id', 'first_name', 'surname', 'email1', 'phone_number1', 'company', 'job_title'])
+        .orderBy('first_name', 'asc')
        .paginate(page, perPage)

At Line 90, within the store method, we return just the id property after the contact is created or edited. Why? The frontend is will be programmed to redirect to the Contact View page using the id of the newly-created or edited contact. We do not need to send down the entire details of the contact. Rather, we need just the id of the created contact. The frontend will use that id to navigate to the Contact View page and the full details of the contact will be fetched by that view. The same applies when we edit a contact at Line 161 (within the update method).

At Line 90:

-      return response.created({ message: 'Contact was created', data: contact })
+      return response.created({ message: 'Contact was created', data: contact.id })

At Line 90:

-      return response.created({ message: 'Contact was edited', data: requestedContact })
+      return response.created({ message: 'Contact was edited', data: requestedContact?.id })

Save the ContactsController file. Start the API server.

cd api && yarn serve

Back to the frontend. We'll begin by improving the types on the frontend.

Open the ui/src/types/index.ts file. Refer to this snapshot of the file. Copy-and-paste the content of the snapshot into the ui/src/types/index.ts file.

At Line 98, we allow the response.data.data property to allow strings value. This is important since we are now returning the id after creating or editing a contact.

-  data: Record<string, unknown>;
+  data: Record<string, unknown> & string;

From Lines 120 - 128, we define the ValidationError interface to properly type the validation errors we receive from the API server.

interface ValidationError {
  data?: {
    error?: {
      messages?: {
        errors?: Array<{ field: string; message: string; rule: string }>;
      };
    };
  };
}

At Line 131, we add the ValidatorError interface to the HttpError interface:

export interface HttpError extends AxiosError {
-   response?: HttpResponse;
+   response?: HttpResponse & ValidationError;
}

Improve the axios.ts Boot File

Open the ui/src/boot/axios.ts file. Refer to this snapshot of the file. Copy-and-paste the content of the snapshot into the ui/src/boot/axios.ts file.

At Line 64, improve the path of the validation error in the error response object:

-        const validationErrors = error?.response?.data?.errors;
+        const validationErrors = error?.response?.data?.error?.messages?.errors;

Improve the contacts Module actions.ts File

Open the ui/src/store/contacts/actions.ts file. Refer to this snapshot of the file. Copy-and-paste the content of the snapshot into the ui/src/store/contacts/actions.ts file.

Within the LOAD_CURRENT_CONTACT action, at Line 21, improve the typing of the response.data.data property:

-          const currentContact = response.data.data as Contact;
+          const currentContact = response.data.data as unknown as Contact;

Then below the LOAD_CONTACTS action, add new action named: CREATE_CONTACT as shown below:

  CREATE_CONTACT(
    ctx,
    {
      editMode,
      payload,
      contactId,
    }: { editMode: boolean; payload: Contact; contactId?: string }
  ): Promise<Contact["id"]> {
    console.log(payload);

    return new Promise(async (resolve, reject) => {
      if (!editMode) {
        await api
          .post("/contacts", payload)
          .then((response: HttpResponse) => {
            const newContactId = response.data.data as Contact["id"];
            return resolve(newContactId);
          })
          .catch((error) => reject(error));
      } else {
        await api
          .put(`/contacts/${contactId}`, payload)
          .then((response: HttpResponse) => {
            const editContactId = response.data.data as Contact["id"];
            return resolve(editContactId);
          })
          .catch((error) => reject(error));
      }
    });
  },

What's going on in this new action?

We are not using any of the getters, commit, or dispatch properties of the ctx. So, no need to destructure the ctx object. The action receives an object containing two mandatory properties (editMode and payload) and one optional property (contactId). The payload property contains the form object of the contact we want to edit or create. The editMode property indicates if the payload is sent in edit mode or otherwise. If false, the api.post() method will be used to create a new contact and the contactId property is not necessary. The response will contain the id of the newly-created contain which we resolve so that it is accessible in the initiator function in the CreateContact.vue component and used to redirect to the Contact View page. If editMode is true, then the api.put() method will be used to edit the contact and the contactId property is required. Again, the response will contain the id of the edited contact.

Implement Contact Creation and Editing in the CreateContact.vue Component

Open the ui/src/pages/contacts/CreateContact.vue file. Refer to this snapshot of the file. Copy-and-paste the content of the snapshot into the ui/src/pages/contacts/CreateContact.vue file.

At Line 69, we import the useRouter hook from vue-router.

+ import { useRouter } from "vue-router";

Within the setup hook, we instantiate the router object at Line 95.

+ const router = useRouter();

At Line 298, we manually create the properties of the object returned from the submitPayload computed property. This is because the object returned from the earlier implementation does not work when sent off via an API request.

    const submitPayload = computed(() => ({
      birthday: form.birthday.value,
      city: form.city.value,
      company: form.company.value,
      country: form.country.value,
      email1: form.email1.value,
      email2: form.email2.value,
      firstName: form.firstName.value,
      jobTitle: form.jobTitle.value,
      notes: form.notes.value,
      phoneNumber1: form.phoneNumber1.value,
      phoneNumber2: form.phoneNumber2.value,
      postCode: form.postCode.value,
      state: form.state.value,
      streetAddressLine1: form.streetAddressLine1.value,
      streetAddressLine2: form.streetAddressLine2.value,
      surname: form.surname.value,
      website: form.website.value,
    }));

At Line 318, we make the submitForm asynchronous since we need to await the store actions.

At Line 331 to 347, we dispatch the contacts/CREATE_CONTACT action with the appropriate payload when the component is in editMode or otherwise.

When a non-error response is gotten, we fetch the edited or created contactId from the callback function of the then promise method.

At Line 338 to 341, we notify the user when the contact is created or edited.

At Line 343 to 346, we navigate to the view_contact route using the contactId as the route parameter.

Those are all the changes for this branch. Save all files, serve both the frontend and API server. Open the homepage to load the contact table. Click on a contact to view it. Edit the contact with new values. On the top toolbar, click Create and click New Contact. Fill the contact form and submit to create a new contact. Play around with the edit and creation processes.

# If the frontend is not served, serve it:
cd ui
yarn serve

# Split the terminal and serve the backend if it is not served
cd api
yarn serve
git add .
git commit -m "feat: complete creation and update of contacts from the frontend"
git push origin 19-create-and-update-contacts-from-the-frontend
git checkout master
git merge master 19-create-and-update-contacts-from-the-frontend
git push origin master