Search by

    Terakhir diperbaharui: Nov 4, 2020

    Integrasi API

    Saatnya kita menggunakan REST API yang sudah kita buat untuk menghandle data DinoTes.

    Dengan REST API, semua data notes akan disimpan ke dalam database MongoDB dan tidak lagi menggunakan localStorage.

    Hal ini akan membuat data pada aplikasi DinoTes dapat diakses dengan browser yang berbeda-beda.

    Berbeda dengan localStorage dimana data tersebut hanya bisa diakses menggunakan browser dimana localStorage tersebut digunakan.

    Persiapan

    Clone Repository

    Jika kamu tidak mengikuti tutorial ini dari awal, kamu bisa clone project DinoTes dari github.

    1. Jalankan perintah berikut untuk clone repository
    1$ git clone https://github.com/devsaurus-class/dinotes-app.git
    1. Pindah ke branch backend-refactoring
    1$ git checkout backend-refactoring

    Fetch Library

    Ada banyak library yang bisa digunakan untuk bekerja dengan API, contohnya Axios atau Fetch.

    Kita akan menggunakan Fetch, karena Fetch adalah library bawaan yang sudah ada di dalam browser, sehingga tidak perlu menginstall package baru.

    Mengingat REST API yang kita buat juga sangat sederhana maka menggunakan Fetch sudah lebih dari cukup.

    CORS

    Perlu diperhatikan bahwa REST API dan Front End dari DinoTes berjalan pada port yang berbeda.

    REST API pada port 3001 sedangkan Front End pada port 3001.

    Begitu juga saat aplikasi dalam kondisi live / in production, aplikasi client dan server akan menggunakan port yang berbeda bahkan domain atau subdomain yang berbeda.

    Sebagai contoh kita bisa menjalankan aplikasi DinoTes menggunakan app.dinotes.com sedangkan REST API menggunakan domain api.dinotes.com.

    Untuk alasan keamanan library fetch yang ada di dalam browser tidak bisa mengambil data dari external.

    Kecuali kita menggunakan mekanisme Cross-Origin Resource Sharing (CORS).

    Dengan mekanisme ini kita bisa mengambil data dari server yang memiliki domain atau port yang berbeda dengan aplikasi client.

    Untuk mengaktifkannya kita perlu menambahkan package cors pada REST API.

    1. Install package cors
    1$ yarn add cors
    1. Tambahkan sebagai middleware

    api/server/js

    1const express = require('express');
    2const { MongoClient } = require('mongodb');
    3const bodyParser = require('body-parser');
    4const cors = require('cors');
    5
    6const routes = require('./routes');
    7const handleErrors = require('./middlewares/errorHandler');
    8
    9const app = express();
    10const port = 3001;
    11
    12// Connection URL
    13const url = 'mongodb://localhost:27017';
    14// Database Name
    15const dbName = 'dinotesDB';
    16
    17app.use(cors());
    18
    19app.use(bodyParser.urlencoded({ extended: true }));
    20...

    Development

    Karena kita menjalankan aplikasi client dan server di port yang berbeda, maka saat proses development kita perlu menjalankannya bersamaan.

    Untuk client jalankan perintah yarn start.

    Untuk REST API jalankan perintah nodemon ./api/server.js.

    Menampilkan Notes

    Kita mulai dari menampilkan data notes dari MongoDB.

    Step by step

    1. Update component yang bertugas untuk menampilkan semua note yaitu file src/components/NotesList.js
    1import React, { useState, useEffect } from 'react';
    2import styled from 'styled-components';
    3import { Link } from 'react-router-dom';
    4
    5const NotesListContainer = styled.div`
    6 display: flex;
    7 flex-direction: column;
    8 min-width: 30vw;
    9 text-align: left;
    10 margin: 1rem;
    11 padding: 1rem;
    12 border: 2px solid #a0aec0;
    13 border-radius: 5px;
    14`;
    15
    16const List = styled.ul`
    17 list-style: none;
    18`;
    19
    20const ListItem = styled.li`
    21 margin: 0.5rem;
    22`;
    23
    24const Separator = styled.hr`
    25 width: 90%;
    26 margin: -1px;
    27 background-color: #edf2f7;
    28 color: #edf2f7;
    29`;
    30
    31const NotesList = () => {
    32 const [notes, setNotes] = useState(null);
    33
    34 useEffect(() => {
    35
    36 async function fetchData() {
    37 const response = await fetch('http://localhost:3001/notes');
    38 const data = await response.json();
    39 setNotes({data});
    40 }
    41
    42 fetchData();
    43
    44 }, []);
    45
    46 const listItems =
    47 notes &&
    48 notes.data.map((note) => {
    49 return (
    50 <ListItem key={note._id}>
    51 <h4>
    52 <Link to={`/edit/${note._id}`}>{note.title}</Link>
    53 </h4>
    54 <p>{note.note.slice(0, 101)}</p>
    55 <Separator />
    56 </ListItem>
    57 );
    58 });
    59
    60 return (
    61 <NotesListContainer>
    62 <List>{listItems}</List>
    63 </NotesListContainer>
    64 );
    65};
    66
    67export default NotesList;

    Sebelumnya kita menggunakan sebuah reusable function bernama getLocalStorageData() untuk mendapatkan data dari local storage.

    Sekarang kita menggunakan sebuah async function bernama fetchData() untuk mendapatkan data dari MongoDB lewat sebuah HTTP request GET yang dikirim ke REST API.

    Jika kita melihat dokumentasi React, proses fetching data sebaiknya dilakukan di dalam lifecycle method componentDidMount().

    Karena kita menggunakan Function Class Component maka function fetchData() kita taruh di dalam hooks useEffect, sehingga proses fetching data dilakukan setelah semua component berhasil di mount.

    Kemudian component akan diupdate setelah fetching data selesai.

    1...
    2 // inisiasi sebuah state bernama notes
    3 const [notes, setNotes] = useState(null);
    4
    5 useEffect(() => {
    6
    7 // deklarasi function fetchData()
    8 async function fetchData() {
    9 const response = await fetch('http://localhost:3001/notes');
    10 const data = await response.json();
    11 setNotes({data});
    12 }
    13
    14 // eksekusi fetchData()
    15 fetchData();
    16
    17 //empty dependency sehingga useEffect hanya dieksekusi satu kali
    18 }, []);
    19 ...

    Dan karena fetching data dilakukan setelah semua component dirender, maka kita perlu menerapkan conditional rendering:

    1...
    2 const listItems =
    3 // jika notes bernilai null tidak ada element yang akan dirender
    4 // jika notes sudah berisi data dari proses fetching maka render element
    5 notes &&
    6 notes.data.map((note) => {
    7 return (
    8 <ListItem key={note._id}>
    9 <h4>
    10 <Link to={`/edit/${note._id}`}>{note.title}</Link>
    11 </h4>
    12 <p>{note.note.slice(0, 101)}</p>
    13 <Separator />
    14 </ListItem>
    15 );
    16 });

    Jika tidak maka akan muncul error yang menginformasikan bahwa tidak ada data yang bisa dijadikan dasar untuk melakukan render pada fase mounting.

    Menambahkan Note

    Update file src/components/AddNoteForm.js

    Tepatnya pada handler handlerSubmit() karena handler ini bertugas untuk mengirimkan data dari form ke REST API.

    Update handlerSubmit() dari:

    1...
    2 const handleSubmit = (e) => {
    3 const notes = getLocalStorageData('notes');
    4
    5 const noteId = uuidv4();
    6
    7 notes.push({ ...state, id: noteId });
    8
    9 localStorage.setItem('notes', JSON.stringify(notes));
    10
    11 setIsSuccess(true);
    12
    13 e.preventDefault();
    14 };
    15 ...

    menjadi:

    1...
    2 const handleSubmit = (e) => {
    3 const options = {
    4 method: 'POST',
    5 headers: { 'Content-Type': 'application/json' },
    6 body: JSON.stringify(state)
    7 };
    8
    9 async function fetchData() {
    10 const response = await fetch('http://localhost:3001/note', options);
    11 if (response.ok) {
    12 setIsSuccess(true);
    13 }
    14 }
    15
    16 fetchData();
    17
    18 e.preventDefault();
    19 };
    20...

    Kita membuat sebuah object bernama options yang berisi informasi HTTP method, headers dan body.

    Perlu diperhatikan karena kita menggunakan format JSON untuk mengirim data ke REST API maka kita perlu menambahkan baris code berikut pada Express.

    api/server.js

    1...
    2 app.use(bodyParser.json());
    3...

    Edit & Hapus Note

    Update code dari component EditNoteForm.js yang berisi handler untuk menampilkan, mengupdate dan menghapus satu note:

    1/* eslint-disable no-underscore-dangle */
    2import React, { useEffect, useState } from 'react';
    3import { useLocation, useHistory } from 'react-router-dom';
    4import { Form, FormGroup, Label, Input, TextArea } from './ui/Form';
    5import Button from './ui/Button';
    6import Message from './ui/Message';
    7
    8const EditNoteForm = () => {
    9 const location = useLocation();
    10 const history = useHistory();
    11 const [currentNote, setCurrentNote] = useState({ title: '', note: '' });
    12 const [isSuccess, setIsSuccess] = useState(false);
    13
    14 useEffect(() => {
    15 const noteId = location.pathname.replace('/edit/', '');
    16
    17 async function fetchData() {
    18 const response = await fetch(`http://localhost:3001/note/${noteId}`);
    19 const data = await response.json();
    20 setCurrentNote(data);
    21 }
    22
    23 fetchData();
    24 }, []);
    25
    26 const handleTitleChange = (e) => {
    27 setCurrentNote({ ...currentNote, title: e.target.value });
    28 };
    29
    30 const handleNoteChange = (e) => {
    31 setCurrentNote({ ...currentNote, note: e.target.value });
    32 };
    33
    34 const handleSubmit = (e) => {
    35 const options = {
    36 method: 'PUT',
    37 headers: { 'Content-Type': 'application/json' },
    38 body: JSON.stringify(currentNote)
    39 };
    40
    41 async function submitData() {
    42 const response = await fetch(`http://localhost:3001/note/${currentNote._id}`, options);
    43 if (response.ok) {
    44 setIsSuccess(true);
    45 }
    46 }
    47
    48 submitData();
    49
    50 e.preventDefault();
    51 };
    52
    53 const handleDeleteNote = () => {
    54 const options = {
    55 method: 'DELETE',
    56 headers: { 'Content-Type': 'application/json' }
    57 };
    58
    59 async function deleteData() {
    60 const response = await fetch(`http://localhost:3001/note/${currentNote._id}`, options);
    61 if (response.ok) {
    62 history.push('/');
    63 }
    64 }
    65
    66 deleteData();
    67 };
    68
    69 const { title, note } = currentNote;
    70
    71 return (
    72 <>
    73 {isSuccess && <Message text="Data berhasil disimpan" />}
    74 <Form onSubmit={handleSubmit}>
    75 <FormGroup>
    76 <Label>Title</Label>
    77 <Input type="text" name="title" value={title} onChange={handleTitleChange} />
    78 </FormGroup>
    79 <FormGroup>
    80 <Label>Note</Label>
    81 <TextArea name="note" rows="12" value={note} onChange={handleNoteChange} />
    82 </FormGroup>
    83 <FormGroup>
    84 <Button type="submit">Save</Button>
    85 <Button danger onClick={handleDeleteNote}>
    86 Delete
    87 </Button>
    88 </FormGroup>
    89 </Form>
    90 </>
    91 );
    92};
    93
    94export default EditNoteForm;

    Penjelasan code di atas:

    • Proses fetching data note yang akan diupdate terjadi di useEffect() kemudian data tersebut disimpan ke dalam state currentNote
    • Setiap perubahan yang terjadi pada note akan diupdate ke dalam state currentNote melalui handler handleTitleChange dan handleNoteChange
    • Ketika user click button submit maka handler handleSubmit akan mengirimkan request PUT ke REST API untuk mengupdate data di MongoDB
    • Untuk menghapus note, client mengirim request DELETE ke REST API

    Demo: