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.
- Jalankan perintah berikut untuk clone repository
1$ git clone https://github.com/devsaurus-class/dinotes-app.git
- 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.
- Install package cors
1$ yarn add cors
- Tambahkan sebagai middleware
api/server/js
1const express = require('express');2const { MongoClient } = require('mongodb');3const bodyParser = require('body-parser');4const cors = require('cors');56const routes = require('./routes');7const handleErrors = require('./middlewares/errorHandler');89const app = express();10const port = 3001;1112// Connection URL13const url = 'mongodb://localhost:27017';14// Database Name15const dbName = 'dinotesDB';1617app.use(cors());1819app.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
- 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';45const NotesListContainer = styled.div`67891011121314`;1516const List = styled.ul`1718`;1920const ListItem = styled.li`2122`;2324const Separator = styled.hr`2526272829`;3031const NotesList = () => {32 const [notes, setNotes] = useState(null);3334 useEffect(() => {3536 async function fetchData() {37 const response = await fetch('http://localhost:3001/notes');38 const data = await response.json();39 setNotes({data});40 }4142 fetchData();4344 }, []);4546 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 });5960 return (61 <NotesListContainer>62 <List>{listItems}</List>63 </NotesListContainer>64 );65};6667export 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 notes3 const [notes, setNotes] = useState(null);45 useEffect(() => {67 // 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 }1314 // eksekusi fetchData()15 fetchData();1617 //empty dependency sehingga useEffect hanya dieksekusi satu kali18 }, []);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 dirender4 // jika notes sudah berisi data dari proses fetching maka render element5 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');45 const noteId = uuidv4();67 notes.push({ ...state, id: noteId });89 localStorage.setItem('notes', JSON.stringify(notes));1011 setIsSuccess(true);1213 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 };89 async function fetchData() {10 const response = await fetch('http://localhost:3001/note', options);11 if (response.ok) {12 setIsSuccess(true);13 }14 }1516 fetchData();1718 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';78const EditNoteForm = () => {9 const location = useLocation();10 const history = useHistory();11 const [currentNote, setCurrentNote] = useState({ title: '', note: '' });12 const [isSuccess, setIsSuccess] = useState(false);1314 useEffect(() => {15 const noteId = location.pathname.replace('/edit/', '');1617 async function fetchData() {18 const response = await fetch(`http://localhost:3001/note/${noteId}`);19 const data = await response.json();20 setCurrentNote(data);21 }2223 fetchData();24 }, []);2526 const handleTitleChange = (e) => {27 setCurrentNote({ ...currentNote, title: e.target.value });28 };2930 const handleNoteChange = (e) => {31 setCurrentNote({ ...currentNote, note: e.target.value });32 };3334 const handleSubmit = (e) => {35 const options = {36 method: 'PUT',37 headers: { 'Content-Type': 'application/json' },38 body: JSON.stringify(currentNote)39 };4041 async function submitData() {42 const response = await fetch(`http://localhost:3001/note/${currentNote._id}`, options);43 if (response.ok) {44 setIsSuccess(true);45 }46 }4748 submitData();4950 e.preventDefault();51 };5253 const handleDeleteNote = () => {54 const options = {55 method: 'DELETE',56 headers: { 'Content-Type': 'application/json' }57 };5859 async function deleteData() {60 const response = await fetch(`http://localhost:3001/note/${currentNote._id}`, options);61 if (response.ok) {62 history.push('/');63 }64 }6566 deleteData();67 };6869 const { title, note } = currentNote;7071 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 Delete87 </Button>88 </FormGroup>89 </Form>90 </>91 );92};9394export 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: