murtaza

TL;DR

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 enum ReactionType, jadi nanti isinya cuma bisa antara CLAPPING, THINKING, dan AMAZED.

    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 where sessionId, terus count 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 metaSectionnya 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:

  1. Nampilin reaction yang mengekspresikan claps, wow, dan hmm.
  2. Nampilin animasi emoji pas hover.
  3. Munculin animasi pas batch klik atau klik beberapa kali.
  4. Nambahin animasi ke reactions counternya.
  5. 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:


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 ๐Ÿ‘‹

Posted onfeatureswith tags: