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 string
s 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