Bedah Code 03-04-2020 : React Full Stact, Express, PostgreSQL (Part 1)


Artikel ini adalah bagian dari latihan saya dalam membuat aplikasi dengan cara mempelajari code milik orang lain. Cara belajar seperti ini saya gambarkan di artikel berikut : Latihan Programming Dengan Cara 'Membedah' Code Buatan Orang Lain. Code yang kali ini saya bahas adalah code buatan Mohammad Iqbal yang ditulis di website FreeCodeCamp berikut: Full Stack React: How to Build Your Own Blog Using Express, Hooks, & Postgres.

Ini adalah artikel bagian pertama. Bagian kedua bisa ditemukan di sini. Bagian ketiga dapat ditemukan di sini.

-----

Yang pertama dilakukan adalah menyimpan starting project React ke local (ke komputer kita), lalu memasukkannya ke file Client.
Lalu di terminal di home folder install express-generator untuk membuat boilerplate express dengan otomatis, yaitu dengan ketik “npm install -g express-generator”.
Lalu kita membuat folder Server. Di terminal di folder Server ketik “express”. Setelah ini akan terbentuk file-file express dan folder yang diperlukan dengan sendirinya.
Lalu hapus folder routes, views, public. Pindahkan app.js ke folder main di dalam folder Server.
Modifikasi app.js sesuai instruksi. Lalu di file www ubah port jadi port 5000.
Lalu di terminal folder Server install library pg, helmet, dan cors (“npm install pg helmet cors”).
cors adalah library yang menghubungkan app React kita dengan server Express.
helmet adalah library keamanan yang memodifikasi http headers supaya http requests kita lebih aman.
pg adalah library yang menghubungkan server dengan psql database.
Hubungkan server dengan app React kita dengan cara menambahkan "proxy": http://localhost:5000 di package.json di foder Client.
Dengan adanya property proxy tersebut, app React kita yang ada di localhost:3000 dapat membuat request sebagai localhost:5000. Jadi pathnya sama antara app React kita dengan app server.

---
Selanjutnya kita akan membuat file routes.js di folder main di bagian Server. Isi filenya masih sederhana yaitu panggilan json ‘hello world’. Di situ ada instansiasi Router juga. Dengan router ini kita membuat panggilan sederhana yaitu membuat router /api/hello lalu memanggil ‘hello world’.
Lalu kita menambahkan method app.use di bagian app.js di bagian Server. Sepertinya untuk mengarahkan app kita ke router yang sudah kita buat.
Selanjutnya di file home.js di folder Client/src/hooks kita tambahkan useEffect yang isinya method axios untuk memanggil route /api/hello. Di dalam method itu ada keyword next yang assign state variable res nya jadi res.data. Axios dipakai untuk request ke server. Sebelulmnya, kita buat elemen state dengan method useState untuk mengambil variable dari route. Hasilnya mestinya kita akan me-render state yaitu tulisan “hello world” di bagian paragraf laman “home” Jadi dengan variable res.data di dalam method axios.get ke route /api/hello di dalam method useEffect kita mengambil variable dari dalam route untuk ditampilkan di laman. Semua method itu dibalut dalam function useEffect. Sekadar catatan, method atau function di dalam useEffect akan dieksekusi setelah terjadi render laman

Selanjutnya akan dijelaskan tentang library axios, react router, dan express router. Axios dipakai untuk menghubungkan frontend dengan backend server express, yaitu lewat route /api/. Express router (yaitu route yang ada di dalam folder Server) dipakai untuk berhubungan dengan database. Express lebih dipilih daripada axios untuk membuat http requests karena express lebih aman. Axios dipilih untuk mengelola http request yang asynchronous di app React kita.

---
Selanjutnya kita akan membuat database. Pertama, kita membuat file schema.sql di folder main di Server. File ini tidak mengeksekusi apa-apa. Dia hanya merupakan referensi yang menggambarkan struktur database kita dan adalah kumpulan perintah-perintah yang kita akan eksekusi di terminal PSQL ketika kita membuat database kita di terminal PSQL tersebut.
Jadi di schema.sql tersebut terlihat bahwa kita akan membuat 3 table, yaitu table users, posts, comments. Di table users ada kolom uid, username, email, email_verified, date_crated, last_login. Di posts ada pid, title, body, user_id, author, date_created, like_user_id, likes. Di tabel comments ada kolom cid, author, user_id, post_id, date_created.
Tabel user menampung data user, tabel post menampung data post, tabel comment menampung data komentar. Tabel posts dihubungkan dengan users dengan kolom user_id dan username. Tabel comments dihubungkan dengan table posts dan users dengan kolom username / uid dan pid.
Property INT[} DEFAULT ARRAY[]::INT[] di kolom like_user_id di tabel posts itu maksudnya tipe datanya array angka dengan variabel awal berupa array tipe angka yang isinya kosong.
Di PSQL ada aturan foreign key constraints, di mana kita tidak bisa menghapus baris data yang masih punya tautan dengan tabel lain. Implikasi dari aturan ini adalah untuk menghapus data suatu user, kita harus menghapus data komentar miliknya dulu, lalu setelah itu hapus post miliknya, lalu setelah itu hapus data usernya.
Setelah membuat database baru di terminal PSQL, kita akan menghubungkan database dengan server memakai library pg. pg ini harus di-install di terminal di folder Server. Setelah itu kita membuat code object bernama pool yang menghubungkan server – database. Pool kita instansiasi di file baru yaitu db.js di folder main di Server. Di dalam pool ada property data tentang database yang akan kita pakai beserta password, user, dan portnya.

---

Selanjutnya kita akan membuat route di server yang berisikan perintah CRUD (Create, Read, Update, Delete).
Code route kita ditulis di file routes.js. Isinya adalah route CRUD untuk posts, comments, likes, user profile. Path ke route server ini ditandai dengan /api/. Route server ini mengambil variable dari frontend React lalu diteruskan ke database PSQL. File routes.js kita me-require express, Router, dan pool dari folder db.js.

Yang pertama adalan route untuk mengambil semua post dengan diurutkan sesuai tanggal pembuatan. Kita mengarahkan user ke route melalui function router.get yang mengambil parameter berupa nama route dan callback function. Callback function router ini mengambil parameter barupa req, res dan next. Di dalam callback function router ini kita memanggil function query dari object pool yang memakai parameter berupa perintah SQL dan callback function lagi. Perintah SQL yang dipakai sesuai dengan route yang kita panggil, sementara callback function di query ini memiliki callback function yang mengambil parameter berupa q_err dan q_res. q_res dari function query ini yang sepertinya merupakan variable yang mau kita ambil. q_res kita spesifikasi lagi yaitu q_res.rows lalu kita balut dalam function res.json.\

Selanjutnya adalah route untuk mengambil satu post. Pertama, route ini mengambil variable dari react App dengan req.query.post_id. Lalu variable tersebut diteruskan ke function query untuk database. Lagi-lagi hasil query diambil dari database lewat variable q_res.rows. Variable ini akan diteruskan ke app React.

Selanjutnya adalah route untuk menaruh post baru ke databas. Pertama kita ambil variable array values yang mengambil variable title, body, uid, username dari React app. Lalu ke dalam function query kita masukkan perintah INSERT INTO ke dalam database dengan memasukkan parameter post di atas. Lalu kita buat statement if else untuk menangkap error. Lalu kita panggil juga q_res.rows yang di balut dalam res.json. Yang perlu diperhatikan, ke dalam database kita juga memasukkan variable date_created. Ini kita dapat dengan keyword NOW() di PSQL. Selain itu, dengan function query kita bisa ‘spread’ array values tadi menjadi kolom-kolom table yang diindikasikan dengan VALUES($1, $2, dan sebagainya). Kita juga tetap tulis function res.json(q_res.rows) meskipun kita tidak berniat me-return value apapun.

Selanjutnya sepertinya adalah route untuk memodifikasi post. Function ini mirip dengan posttodb, hanya saja di bagian values ada variable post id (pid) untuk menunjukkan post mana yang mau di-update. Selain itu perintah SQLnya juga UPDATE dan kelanjutannya pada prinsipnya adalah meng-update data di dalam row sesuai values yang kita masukkan. Sementara data date_created kita masukkan perintaah NOW(). Terjadi spread dari values sama seperti posttodb. Di bagian akhir function query ada console log q_res dan q_err. Ini sedikit berbeda dengan function posttodb.

Selanjutnya adalah delete comments. Di sini kita ambil variable post_id dari frontend. Kita masukkan DELETE ke dalam pool.query. Lalu di dalam callback function pool.quer kita panggil q_res.rows dalam res.json dan console.log q_err. Yang perlu diperhatikan adalah bahwa di function delete ini tidak ada spesifikasi id comment, jadi kemungkinan besar ini function untuk menghapus semua komentar dalam suatu post.

Selanjutnya adalah route delete post. Route ini mengambil post_id dari frontend, lalu memasukkan panggilan DELETE di function pool.query. Di bagian callback function pool.query ada panggilann res.json(q_res.rows) dan console log q_err. Yang perlu diperhatikan adalah di method bagian router, kata kuncinya adalah delete, bukan get seperti pada router read sebelumnya.

Bisa disimpulkan bahwa untuk function pool.query, parameter pertama adalah perintah untuk ke database, parameter kedua adalah array variabel yang menjadi target untuk dioperasikan oleh database, parameter ketiga adalah callback function.

Route selanjutnya adalah route put likes. Route ini mengambil variabel user id, post id dari frontend. Kedua variable tersebut ada yang dipisah dan ada yang digabung yaitu dimasukkan dalam satu array yang sama (array values). Perintah ke databasenya agak rumit ini. Jadi perintahnya pada intinya adalah memasukkan user id yang memberi like ke dalam data array like_user_id untuk suatu post lalu menambah jumlah likenya sebesar satu. Callback funtion untuk pool.query-nya adalah if untuk error, console log q_ress dan res.json(q_res.rows). Callback function tiap-tiap route ini mirip-mirip, mungkin harus dipelajari lagi kenapa harus dibedakan.
Selanjutnya adalah route menambahkan komentar ke database. Route ini mengambil value comment, user_id, username, post_id yang dimasukkan ke array values. Sepertinya caranya sama seperti menambah post. Callback function untuk pool.querynya adalah res.json(q_res.rows) dan console log q_err.

Selanjutnya adalah route untuk memodifikasi komentar sepertinya. Route ini mengambil array values yang terdiri comment, user_id, post_id, username, dan comment id). Selanjutnya ada function UPDATE database sesuai parameter values.

Selanjutnya adalah router untuk delete comment. Route ini mengambil value comment id dan functionnya cukup sederhana.

Selanjutnya adalah route untuk mengambil semua komentar dalam suatu post. Mungkin ini untuk ditampilkan buat tiap post. Dia mengambil variable post_id saja, lalu callback function pool.query-nya hanya res.json(q_res.json). Sepertinya route yang fungsinya hanya menampilkan data dari database callback function pool.query-nya hanya itu.

Selanjutnya adalah router untuk menambahkan profile baru. Values yang diambil adalah nickname, email, status verifikasi email yang digabung dalam satu array. Values tersebut dimasukkan dalam database dengan keyword INSERT INTO. Lalu ada keyword ON CONFLICT DO NOTHING yang mungkin mengantisipasi kalau ada kesamaan entri data. Callback function untuk query adalah res.json(q.res.rows).
Selanjutnya adalah route untuk mengambil data suatu profile dari database. Route ini mengambil value berupa email dari frontend. Lalu dari email tersebut dicocokkan dengan data user keseluruhan yang diinginkan untuk diambil. Callback function untuk query-nya adalah res.json(q_res.rows). Di sini value yang diambil dari front end adalah req.query..email. Ini berbeda dari sebelumnya yang memakai body, bukan query.

Selanjutnya adalah route untuk mengambil post yang dibuat suatu user. Variable yang diambil dari frontend adalah user_id. Callback function pool.query-nya adalah req.json(q_res.rows).

Selanjutnya adalah route untuk mengambil profile user dari database berdasarkan username. Kalau route sebelumnya yang mengambil profile user juga itu dia pakai variable email. Sekarang pakai username. Callback function pool.query-nya adalah req.json(q_res.rows).

Selanjutnya adalah route untuk mengambil post berdasarkan username. Kalau di route sebelumnya, dia pakai variable user_id buat ambil post. Kalau sekarang pakai username. Callback function pool.query-nya adalah req.json(q_res.rows).

---

Selanjutnya kita mesti menyimpan data user profile ke database. Pertama kita import axios di autcheck.js. Lalu kita kirimkan data profile ke database lewat route userprofiletodb lalu langsung kita ambil lagi melalui metode get ke route userprofilefromdb. Dalam function get ini kita pakai parameter email. Lalu value yang kita dapat lewat callback function kita masukkan ke function handleAddDBProfile, yaitu res.data. res.data ini isinya adalah email user. Dan data ini yang akan di pasang di global state.
useEffect di dalam AuthCheck ini memiliki parameter kedua yang berupa array. Ini artinya function di parameter pertama di useEffect itu akan dieksekusi hanya ketika parameter kedua mengalami perubahan.

Selanjutnya kita akan menambah action types dan actions. Actions yang ditambahkan adalah set db profile, remove db profile, fetch db posts, remove db posts. Set db profile dan fetch db posts memiliki property payload yang isinya masing-masing adalah profile dan posts. Set db profile sepertinya untuk mengubah global context profile menjadi profile yang diambil dari database. Remove db profile sepertinya menghapus global context tersebut. Fetch db posts sepertinya untuk menetapkan global context yang isinya adalah object posts. Remove db posts sepertinya untuk menghapus object posts tersebut.

Selanjutnya kita akan membuat file baru di bagian Client reducer, yaitu posts_reducer.js. Di file ini ada variable initialState yang isinya adalah property : null. Lalu ada function PostsReducer yang mengambil parameter berupa initialState itu dan action. Dalam PostsReducer ini ada switch statement yang memutuskan kondisi initialState berdasarkan action yang dia terima di dalam parameternya. Pilihannya adalah fetch db posts dan remove db posts. Kalau dia dapat fetch db posts, dia akan menerima property payload yang dibawa oleh object action di dalam parameternya.

Selanjutnya adalah modifikasi AuthReducer. Jadi AuthReducer ini punya initialState dan di parameternya dia terima action juga. Dan isi function ini adalah switch statements antara berbagai action type yang berhubungan dengan authentication. Tambahannya adalah set db profile dan remove db profile. Kalau dapat action types set db profile, initialState dia akan berubah menjadi punya db_profile dengan value payload dari action. Kalau dapat action types remove db profile, property db_profile-nya akan jadi null.

Selanjutnya perubahan ini akan ditambahkan di component Context.Provider.
Di file context_state_config.js ditambahkan bagian Post Reducer. Di sini ada deklarasi useReducer dengan array statePosts dan dispatchPosts. Sementarai di sisi function useReducernya terdapat parameter PostsReducer dan PostsReducer.initialState. Di sisi array, funtion dispatchPosts berfungsi untuk menerima variabel yang masuk dari bagian eksternal PostsReducer untuk kemudian di olah di component PostsReducer. DispatchPosts ini dibungkus dalam function handleSetPosts yang memasukkan action set db posts, sementara dispatchPosts dibungkus dalam function handleRemovePosts yang memasukkan actions remove db posts ke dalam dispatchPosts tersebut. Nanti parameter actions tersebut akan diteruskan ke komponen PostsReducer yang kemudian akan memutuskan harus me-return apa tergantung action yang diterima. Pada function handleSetPosts, selain action itu sendiri, function itu juga meneruskan payload berupa posts ke PostsReducer.

Selanjutnya adalah modifikasi Auth Reducer di file context_state_config.js. Tambahannya adalah function handleDBProfile dan handleRemoveDBProfile. Kedua function itu befungsi memberikan parameter kepada function dispatchAuth yang nantinya akan diteruskan ke component Auth Reducer yang nantinya akan diolah dalam switch statement di dalamnya. Di starter file sebelumnya sudah ada deklarasi useReducer untuk AuthReducer. Di tahap penambahan server ini, ada tambahan deklarasi useReducer tapi sebenernya tambahan ini menggantikan yang sebelumnya sudah ada.

Selanjutnya, function handle yang sudah ditambahkan tadi akan dipakai di Context.Provider. Pertama, kita menetapkan context dbProfileState-nya adalah stateAuthReducer.db_profile, yaitu property yang  tercatat di stateAuthReducer. Value handleAddDBProfile kita tetapkan memiliki property berupa profile dan ini akan diteruskan ke handleDBProfile yang telah kita deklarasikan di atas. Kita juga menetapkan function handleRemoveDBProfile di sini.
Kemudian, untuk PostsState, kita menetapkan property posts yang ada di PostReducer sebagai context postsState. Kemudian, kita akan mendeklarasikan functio handleAddPosts dan handleRemovePosts di bagian value dari Context ini. Kedua statement itu pertama akan mengarahkan input beserta property di dalamnya (dalam hal ini adalah posts untuk function handleAddPosts) ke function di ContextState, lalu dari situ akan diarahkan ke component AuthReducer yang akan memasukkan actions yang dibawanya ke switch statements.

Comments