Engga nyangka bikin ini doang bisa sampe hampir tiga hari loh, tapi emang semuanya dari 0 sih. CUMA bikin fitur reactions aja, iya itu yang ngikut-ngikut di bawah ๐
Engga bikin analisisnya dulu, gas langsung bikin database, trial & error dan refactoring. Antara gabisa sama males, tapi yang jelas pengen langsung ngoding sih ๐
Sekalian di artikel ini saya jabarin deh, fiturnya apa aja, gimana konsep bikinnya, tapi bukan tutorial step by step ya.
Siapa tau kalian ada yang mau bikin juga ya kan, jadi bisa nyontek lah sedikit-sedikit mah, tapi ambil yang 'baiknya' aja we ya.
Gasss langsung lah...
Fitur
Pertama saya mikirin gambaran kasarnya dulu mau dibikin kaya gimana si reactions ini teh.
Dan dapetlah, kira-kira fiturnya kaya gini:
-
Multiple Reactions
Ada claps, wow dsb. dan sebisa mungkin gampang kalau misal kedepannya mau nambahin reaction yang lain.
-
Batch Reactions
Bisa ngasih beberapa kali reaction yang sejenis, misal 20x claps, 10x wow dsb.
-
Section Reactions
Bisa tau dimana user ngasih reaction, misal ngasih 10x claps di section yang judulnya 'Fitur' kaya section ini.
Konsep
Jadi sebenarnya yang lama tuh bikin REST API, banyak refactoring, lama bikin skema databasenya dan lain-lain yang ke arah BE nya sih.
Pasti banyak yang kurang tepat, harap dimaklum ya, bukan anak BE soalnya โ
Database
Jujur setelah tiga tahun gitu saya baru berurusan lagi sama database. Terakhir ngurusin database teh pas bikin aplikasi ujian akhir perkuliahan.
Pertama nentuin dulu mau hosting dimana nih. Masalah SQL noSQL ngga masalah, karena masih coba-coba, jadi nyoba cari yang gratisan wkwk dan dapet hostingan MongoDB Atlas yang shared, lumayan lah.
Karena ORM nya juga saya pake prisma, jadi kayanya ngga terlalu ribet sih kalau migrasi ke database yang lain.
Mari ke skema database. Ini fokus ke fitur reactionsnya aja ya, sebenarnya ada field buat views dan shares tapi sengaja ngga saya masukin.
Pertama, saya bikin table ContentMeta. Ini buat nyimpen data postingannya.
Gaperlu nyimpen title, date, konten dll karena emang data postingannya langsung build secara lokal dari mdx, jadi cuma nyimpen slugnya aja.
model ContentMeta {
id String
slug String
createdAt DateTime
}
Kemudian ini nih yang penting, table Reaction, buat nyimpen si reactionsnya.
model Reaction {
id String
type ReactionType
section String
count Int
sessionId String
createdAt DateTime
contentId String
}
enum ReactionType {
CLAPPING
THINKING
AMAZED
}
Let's break it down slowly:
-
type
Penting banget, karena pake multiple reaction,
type
ini wajib pake. Tipe datanya saya pake predefined enumReactionType
, jadi nanti isinya cuma bisa antaraCLAPPING
,THINKING
, danAMAZED
.Kalau mau nambahin reaction yang lain, bisa nambahin item baru ke enumnya. Opsi lain sekalian gasin aja ubah tipe datanya jadi String, jadi misal kalau mau pake reaction semua emoji juga bisa.
Kenapa ngga bikin relasi ke table baru aja?
Bisa, cuma saya mikir kayanya bakal ribet ah nanti querynya, pake enum juga kayanya udah cukup, sekalian ngandelin type safetynya juga sih โ
-
section
Cuma nyimpen judul section yang active saat user klik reaction nya. Misal pas user baca yang judulnya 'Database', maka section ini nanti isinya 'database'.
Cuma ngandelin IntersectionObserver, observe semua judul section yang ada, yang terakhir 'isIntersecting' itu dijadikan nilai section ini.
-
count
Ini buat jumlah batch reactionnya. Jadi pas ngeklik reaction tuh sebenarnya ngga langsung kirim ke API, tapi delay dulu sekian milliseconds, debounce lah.
Sederhananya gini: pas klik reaction beberapa kali dalam selang waktu singkat tuh dikumpulin dulu jumlah kliknya (misal 10 klik), beres itu langsung kirim ke API, dan jumlah kliknya simpen di
count
ini.Buat saya cara ini lebih make sense, ketimbang request ke API 10x dan bikin 10 row reaction dengan data yang sama. Sekalian buat hemat storage databasenya.
-
sessionId
Buat nyimpen 'siapa' yang ngereaction. Bukan nama atau identitas user sih, tapi saya pake ip address user.
Eits, ngga mentah simpen ip addressnya juga, karena ini kan termasuk data yang sangat sensitif ya buat user.
Saya hash tuh ip address, plus 'digaremin' juga biar mantepp hehe. Jadi ngga ada yang tau tuh ip address user, termasuk saya yang bikin.
Kira-kira kodenya gini:
export const getSessionId = (req: NextApiRequest) => { const ipAddress = req.headers['x-forwarded-for'] || 'localhost'; // hashes the user's IP address and combines it with // a salt to create a unique session ID that preserves // the user's privacy by obscuring their IP address. const currentSessionId = createHash('md5') .update(ipAddress + process.env.SALT_IP_ADDRESS, 'utf-8') .digest('hex'); return currentSessionId; };
Atau kalau mau inspect langsung juga gasss kesini.
Terus kenapa harus ada sessionId?
Ini digunain buat 'limit' atau ngebatesin jumlah reactionsnya. Saya groupBy
type
wheresessionId
, teruscount
nya dikalkulasi.Kalau hasilnya udah nyentuh max limit, berarti user gabisa ngasih reaction lagi.
REST API
Ini sih cuma butuh satu endpoint, saya bikinnya /reactions
terus cuma bisa nerima method POST gitu. Nerima datanya slug
, type
, section
, dan count
.
Seperti yang udah dijelasin sebelumnya, semua data yang dikirim dihandle di Front-End. Kecuali yang sessionId
. sessionId
ini digenerate pas request API nya.
Eh ada deng tambahin satu endpoint lagi, GET /content
. Ini nampilin detail jumlah reactions slug
saat ini, buat digunain di komponennya nanti.
Dari dua table sebelumnya, bisa dapet data ini (response /content
):
{
"meta": {
"reactionsDetail": {
"CLAPPING": 65,
"THINKING": 45,
"AMAZED": 48
}
},
"metaUser": {
"reactionsDetail": {
"CLAPPING": 15,
"THINKING": 15,
"AMAZED": 15
}
},
"metaSection": {
"fitur": {
"reactionsDetail": {
"CLAPPING": 0,
"THINKING": 4,
"AMAZED": 0
}
},
"database": {
"reactionsDetail": {
"CLAPPING": 0,
"THINKING": 0,
"AMAZED": 6
}
},
}
}
meta
itu jumlah keseluruhan reactions dari semua user, metaUser
jumlah reactions user saat ini, dan metaSection
jumlah reactions pada section tertentu.
Mungkin ada yang ngeuh, itu metaSection
nya kenapa ngga dibikin array aja?
Jujurr.... gatau juga sih ๐
Tapi karena di FE logic component saya cuma ngambil data per section (ngga looping), saya lebih suka ngambil datanya dengan cara metaSection[section]
ketimbang array yang harus difilter dulu ๐
Component
This is the part that I enjoy the most.
Pertama, untuk ngambil data dari API, saya pake SWR. Dikutip dari situs SWR nya sendiri:
With SWR, components will get a stream of data updates constantly and automatically. And the UI will be always fast and reactive.
Enak banget sih pake SWR ini, apalagi mutation datanya.
Sekarang bikin komponen reactionsnya ๐ฅณ
Detail komponen:
- Nampilin reaction yang mengekspresikan
claps
,wow
, danhmm
. - Nampilin animasi emoji pas hover.
- Munculin animasi pas batch klik atau klik beberapa kali.
- Nambahin animasi ke reactions counternya.
- Ganti emoji pas udah kena limit.
Untuk animasi emojinya (point 2) saya ambil disini ya.
Untuk urusan animasi masih setia pake framer motion ๐
Pas user klik reaction, kan muncul animasi tuh, pas event onClick
cuma push yang nilai animasi x
sama y
-nya random. Nilai animasi tadi dirender jadi componentnya framer motion.
Dengan nilai x
dan y
yang random, jadi pas klik beberapa kali tuh ngga monoton animasinya ke arah situ-situ aja.
Bingung ya? hehe maaf, coba cek full kodenya.
Hasil Akhir
Ini dia hasil akhirnya, coba kasih reactions, sampe limit, ada animasinya tuh ๐
Meskipun masih butuh banyak penyesuaian baik dari styles, gambar, layout ataupun performance, tapi saya senang dengan hasil akhir saat ini.
Summary
Meskipun pembuatan fitur ini cukup challenging terutama di BE nya, but I quite enjoy all the process. Meskipun butuh hampir tiga hari cuy ๐
Rekap tech stacks / librari yang digunakan:
- MongoDB Atlas untuk database hosting.
- Prisma untuk ORM nya.
- SWR untuk ngambil data dari API.
- Framer Motion untuk animasi.
- Animated Fluent Emoji
WAITTT, terus itu section reactions? API nya kan udah nyediain
metaSection
, dipake buat apa?
Kalau datanya udah ada aman lah ya, meskipun implementasinya emang belum. Tapi emang udah ada rencana dan kebayang mau dipake kaya gimana. Tunggu aja lah ya ๐
Tapi kayanya bakal update di Twitter, ngga bakal dibikinin postingan khusus.
HATUR NUHUN udah baca sampe selesai. Sampai jumpa minggu depan di postingan selanjutnya ๐