Waktu saya ditanya apakah bisa membuat photobooth digital untuk pernikahan, pertanyaan pertamanya bukan soal desain atau layout. Pertanyaannya: fotonya disimpan di mana?
Jawaban yang biasa diberikan adalah “di server, nanti tamu bisa lihat galeri”. Dan jawaban itu membawa tiga masalah sekaligus: biaya penyimpanan, tanggung jawab hukum atas foto orang, dan satu antrean upload yang harus jalan di jaringan gedung yang belum tentu kuat.
Saya memilih jawaban yang berbeda. Foto tidak pernah meninggalkan perangkat tamu. Tidak ada server, tidak ada database, tidak ada galeri pusat. Semua terjadi di browser.
Kenapa ini keputusan teknis, bukan slogan
Mudah sekali menulis “privacy-first” di halaman marketing. Tapi privasi bukan fitur yang bisa ditempel belakangan; dia adalah konsekuensi dari arsitektur.
Kalau saya membangun photobooth ini dengan backend, maka setiap foto tamu harus melewati: kamera → memori → request HTTP → penyimpanan server → database → kembali ke client. Setiap panah di rantai itu adalah tempat data bisa bocor, tertinggal, atau tersalin ke backup yang tidak pernah saya bersihkan.
Dengan menghapus seluruh rantai setelah “kamera”, saya tidak perlu menjanjikan apa pun. Tidak ada foto di server karena tidak ada server yang menerima foto. Klaim privasinya jadi fakta arsitektur, bukan komitmen moral yang harus saya jaga secara manual.
Ada efek samping yang saya tidak antisipasi: masalah jaringan hilang sekaligus. Tidak ada upload berarti tidak ada antrean upload, tidak ada progress bar yang macet di 90%, dan tidak ada foto yang hilang karena tamu menutup tab sebelum unggahannya selesai. Jaringan gedung yang putus-nyambung tidak lagi jadi risiko.
Bagaimana sebenarnya ia bekerja
Alurnya lebih sederhana dari yang biasanya orang bayangkan.
Kamera. Saya memakai getUserMedia untuk mengambil stream dari kamera
depan. Stream itu ditempel ke elemen <video>, lalu tamu menekan tombol
jepret. Saat itulah frame ditangkap.
Canvas. Menangkap frame berarti menggambar video ke <canvas> dengan
drawImage, lalu memanggil toBlob() untuk mendapat data gambar. Di sini
saya bisa melakukan semua komposisi: memotong ke rasio tertentu, membalik
horizontal supaya terasa seperti cermin, dan menempelkan foto ke dalam template
kolase yang dipilih tamu.
Template. Setiap template adalah gambar latar dengan tiga slot foto. Saya menggambar template ke canvas, lalu menggambar ketiga foto tamu ke posisi yang sudah ditentukan. Hasilnya satu gambar utuh — bukan tiga gambar yang disusun dengan CSS.
Penyimpanan. Ini bagian yang biasanya butuh server. Saya memakai IndexedDB: setiap hasil kolase disimpan sebagai blob dengan id, sehingga tamu bisa membuka “Foto Saya” di kemudian hari tanpa akun, tanpa login. Datanya tinggal di perangkat mereka. Kalau mereka membersihkan data browser, fotonya hilang — dan itu memang konsekuensi yang saya terima, bukan bug.
Unduhan. Untuk menyimpan ke galeri HP, saya membuat object URL dari blob dan
memicu unduhan lewat anchor. Di iOS ada beberapa keanehan — atribut download
tidak selalu dihormati untuk blob, jadi di beberapa jalur saya menampilkan
gambar dengan instruksi tekan-lama untuk menyimpan. Tidak elegan, tapi berfungsi.
Yang jadi lebih sulit
Saya tidak mau menggambarkan ini sebagai kemenangan tanpa biaya.
Ukuran memori. Seluruh kolase hidup di memori browser. Di HP kelas menengah dengan kamera 12 megapiksel, satu frame bisa besar. Saya harus mengecilkan resolusi canvas ke ukuran yang wajar sebelum komposisi, kalau tidak tab bisa dibunuh sistem di tengah sesi.
Kompatibilitas. Setiap browser punya perilaku berbeda soal getUserMedia,
orientasi gambar, dan dukungan blob. Safari di iOS adalah yang paling banyak
minta penyesuaian. Saya kehilangan waktu paling banyak bukan di logika kolase,
tapi di memastikan hasilnya sama di empat browser.
Tidak ada analitik. Karena tidak ada backend, saya tidak tahu berapa tamu yang memakai, template mana yang paling dipilih, atau berapa foto yang dibuat. Untuk acara satu malam itu tidak masalah. Untuk produk yang mau diukur, ini keterbatasan nyata.
Tidak ada galeri bersama. Kalau panitia ingin melihat semua hasil, itu mustahil tanpa menambahkan server — dan begitu server masuk, seluruh alasan arsitektur ini runtuh. Jadi ini bukan fondasi yang bisa ditumbuhkan menjadi produk SaaS tanpa dibongkar ulang.
Kenapa tetap saya pilih
Karena untuk kasus ini, tujuannya jelas: satu acara, satu malam, tamu yang ingin pulang membawa sesuatu. Tidak ada kebutuhan jangka panjang yang harus dipertahankan, dan tidak ada data yang pantas saya simpan.
Privasi tamu jadi alasan teknis karena arsitekturnya memang menghapus kemungkinan penyalahgunaan. Saya tidak perlu menulis kebijakan privasi yang menjelaskan retensi data, karena tidak ada data yang ditahan. Itu jauh lebih kuat daripada janji.
Detail pengerjaannya bisa dilihat di halaman proyek photobooth. Kalau kamu sedang membangun sesuatu yang menyentuh data pribadi orang, pertanyaan yang layak diajukan lebih dulu bukan “bagaimana mengamankan data ini”, tapi “apakah data ini perlu ada sama sekali”.
Catatan teknis yang mungkin berguna
Beberapa hal spesifik yang saya pelajari dan mungkin berguna kalau kamu membangun sesuatu yang mirip.
Selalu panggil video.play() setelah stream siap. Di banyak browser,
<video> dengan srcObject dari getUserMedia tidak otomatis mulai memutar.
Kalau layarnya hitam, biasanya bukan kameranya yang gagal — hanya perlu
dipanggil sekali setelah metadata siap.
Perhatikan orientasi gambar. Foto dari kamera HP menyimpan metadata EXIF orientasi. Canvas tidak membacanya. Akibatnya foto bisa tampil miring walau di galeri HP terlihat benar. Solusinya mendeteksi orientasi dari EXIF lalu merotasi canvas secara manual saat menggambar.
Bersihkan object URL. Setiap URL.createObjectURL() menahan blob di memori
sampai halaman ditutup. Kalau tamu mengambil lima foto, itu lima blob besar yang
tidak pernah dilepas. Saya memanggil revokeObjectURL setelah unduhan selesai.
Uji di perangkat nyata, bukan hanya di devtools. Mode responsif di desktop tidak menirukan perilaku kamera HP, batas memori, atau kebiasaan sistem menghentikan tab yang tidak aktif. Bug paling serius yang saya temukan hanya muncul di HP sungguhan dengan memori terbatas.
Pertanyaan yang lebih berguna
Kalau kamu membangun fitur yang menyentuh data orang, urutan pertanyaan yang saya sarankan begini. Pertama: apakah data ini perlu ada sama sekali? Kedua: kalau perlu, apakah perlu ada di server saya, atau cukup di perangkat penggunanya? Ketiga: kalau harus di server, berapa lama, dan siapa yang boleh melihatnya?
Sebagian besar sistem menjawab pertanyaan pertama dengan “iya, karena sudah biasa begitu”, lalu menghabiskan sisa waktunya mengerjakan pertanyaan ketiga dengan susah payah. Padahal kalau pertanyaan pertama dijawab jujur, dua pertanyaan berikutnya sering hilang sendiri.