Skip to main content
10 Agustus 2026 · 9 min read · apmnu

Membangun Platform Keanggotaan APMNU: Dari Dokumen Organisasi ke Satu Sumber Data

Catatan membangun platform APMNU yang menyatukan website publik, portal profesor, panel admin, dan REST API—termasuk keputusan domain, monorepo, serta CI/CD.

apmnu case-study nuxt nestjs typescript turborepo prisma postgresql project-management devops
Membangun Platform Keanggotaan APMNU: Dari Dokumen Organisasi ke Satu Sumber Data

Membangun Platform Keanggotaan APMNU: Dari Dokumen Organisasi ke Satu Sumber Data

Membangun website organisasi sering terlihat seperti pekerjaan membuat halaman profil, berita, dan kontak. Dalam proyek Asosiasi Profesor Muslimat Nahdlatul Ulama (APMNU), kebutuhan sebenarnya jauh lebih besar.

Organisasi memerlukan tempat untuk memperkenalkan diri kepada publik, tetapi juga membutuhkan proses pendaftaran anggota, verifikasi SK Guru Besar, direktori profesor, pengelolaan profil akademik, agenda, liputan, dan ruang operasional untuk pengurus.

Artinya, yang kami bangun bukan hanya sebuah website. Kami membangun platform keanggotaan dengan tiga sisi produk:

  1. website publik untuk masyarakat;
  2. portal mandiri untuk profesor;
  3. panel admin untuk pengurus organisasi.

Saya mengerjakan proyek ini sebagai Project Manager sekaligus Full-Stack Developer bersama tim beranggotakan tiga developer. Dalam artikel ini, saya ingin membahas proses berpikir di balik produknya: bagaimana kebutuhan organisasi diterjemahkan menjadi domain, mengapa kami memilih monorepo, dan keputusan apa saja yang membuat sistem lebih mudah dikembangkan serta dioperasikan.

Tampilan website publik APMNU pada desktop

Masalahnya Bukan Sekadar “Belum Punya Website”

Data guru besar perempuan NU sebelumnya belum tersedia sebagai direktori publik yang mudah ditelusuri. Proses keanggotaan dan verifikasi juga membutuhkan alur yang konsisten, sedangkan informasi organisasi dipublikasikan melalui kanal yang terpisah.

Dari discovery awal, kami menemukan tiga kelompok pengguna dengan kebutuhan berbeda.

Publik

Publik perlu memahami profil APMNU, melihat agenda dan liputan, serta menemukan profesor berdasarkan informasi akademiknya. Dalam konteks tertentu, pengunjung juga perlu menghubungi profesor sebagai calon narasumber.

Profesor

Profesor membutuhkan proses pendaftaran yang jelas dan ruang untuk mengelola data akademiknya sendiri. Informasi seperti riwayat pendidikan, bidang keahlian, publikasi, dan tautan akademik akan lebih akurat jika pemilik profil dapat memperbaruinya langsung.

Pengurus

Pengurus memerlukan panel untuk meninjau pendaftaran, memastikan dasar keanggotaan valid, serta mengelola agenda, liputan, materi, galeri, dan halaman organisasi tanpa bergantung kepada developer untuk perubahan konten harian.

Jika ketiga kebutuhan itu dibangun sebagai sistem terpisah, data akan kembali terfragmentasi. Karena itu, tujuan utama kami adalah membuat satu sumber data yang melayani kebutuhan publik dan internal sekaligus.

Mengubah Kebutuhan Menjadi Alur Produk

Kami menyusun alur keanggotaan seperti berikut:

Calon anggota
    │ mengirim identitas + SK Guru Besar

Pendaftaran


Review admin
    ├── Disetujui
    ├── Perlu revisi + catatan
    └── Ditolak + alasan


Portal profesor
    │ melengkapi profil akademik

Direktori publik terverifikasi

Alur tersebut terlihat sederhana setelah ditulis, tetapi ada keputusan domain penting di dalamnya: status pendaftaran bukan status keanggotaan.

Kami memakai dua kelompok status yang berbeda:

  • status registrasi: PENDING, APPROVED, REJECTED, dan NEED_REVISION;
  • status keanggotaan: ACTIVE, EMERITUS, dan DECEASED.

Pemisahan ini mencegah pertanyaan yang ambigu. Seseorang bisa sudah lolos verifikasi registrasi, tetapi kondisi keanggotaannya berubah di kemudian hari. Jika keduanya disimpan dalam satu field, aturan bisnis akan cepat membingungkan dan rawan salah digunakan.

Dari proyek ini saya kembali belajar bahwa banyak bug bisnis sebenarnya bermula dari kosakata domain yang belum disepakati, bukan dari syntax atau framework.

Arsitektur: Tiga Aplikasi dalam Satu Monorepo

Kami menggunakan monorepo TypeScript berbasis Turborepo dengan struktur utama seperti berikut:

apmnu/
├── apps/
│   ├── api/       # NestJS REST API
│   ├── web/       # website publik + portal profesor, Nuxt 4
│   └── admin/     # panel pengurus, Nuxt 4
└── packages/
    ├── schemas/   # kontrak validasi Zod
    ├── types/     # TypeScript types bersama
    ├── ui/        # komponen Vue bersama
    ├── utils/     # fungsi utilitas
    └── config-*/  # konfigurasi TypeScript dan ESLint

Alur komunikasinya:

Publik ───────────────► Website Nuxt
Profesor ─────────────► Website Nuxt ──┐


                                  NestJS API ──► PostgreSQL
                                       ▲            Prisma

Pengurus ─────────────► Panel Admin ────┘

             Shared Zod schemas digunakan di semua aplikasi

Mengapa panel admin dipisahkan?

Pilihan termudah sebenarnya adalah menaruh halaman admin di aplikasi web yang sama lalu melindunginya dengan role. Kami memilih aplikasi terpisah karena kebutuhan keduanya berkembang ke arah berbeda.

Website publik berorientasi pada kecepatan, kemudahan eksplorasi, dan pengalaman pengguna. Panel admin lebih padat data, memiliki form kompleks, tabel, review dokumen, serta aksi operasional.

Pemisahan ini memberi beberapa manfaat:

  • bundle website publik tidak membawa fitur administratif;
  • batas autentikasi dan tanggung jawab aplikasi lebih jelas;
  • web dan admin dapat dikembangkan serta dirilis secara independen;
  • perubahan dashboard tidak harus mengganggu pengalaman publik.

Trade-off-nya adalah setup awal lebih besar dan sebagian komponen perlu dibagikan melalui packages/ui. Namun, biaya tersebut masuk akal karena kedua aplikasi memang memiliki arah pengembangan yang berbeda.

Shared Zod Schema sebagai Kontrak

Ketika satu API digunakan oleh dua frontend, duplikasi validasi mudah terjadi. Backend memiliki aturannya sendiri, website publik menulis ulang aturan serupa, lalu panel admin membuat versi ketiga. Seiring waktu, ketiganya dapat menyimpang.

Untuk mengurangi masalah itu, kami menaruh Zod schema bersama di packages/schemas. API, website, dan admin menggunakan definisi yang sama untuk struktur request maupun aturan validasi yang relevan.

Secara konseptual:

// packages/schemas
export const registrationSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  // field pendaftaran lainnya
});

Schema tersebut kemudian dapat digunakan di sisi form dan API. Hasilnya bukan hanya lebih sedikit duplikasi, tetapi juga perubahan kontrak menjadi lebih terlihat. Ketika sebuah field berubah, aplikasi yang terdampak dapat ditemukan melalui typecheck dan build.

Bagi saya, shared schema adalah salah satu manfaat paling nyata dari monorepo TypeScript pada produk multi-aplikasi.

Menentukan Batas Verifikasi

Kami menetapkan SK Guru Besar sebagai dasar utama verifikasi keanggotaan. Setelah pendaftaran disetujui, profesor dapat memperbarui informasi akademik non-verifikasi secara mandiri.

Keputusan ini merupakan kompromi antara kontrol dan kemudahan operasi.

Jika setiap perubahan profil harus menunggu approval admin, data memang lebih terkontrol, tetapi pengurus menjadi bottleneck. Sebaliknya, jika seluruh data bebas diubah tanpa batas yang jelas, dasar keanggotaan ikut kehilangan kepastian.

Dengan memisahkan data verifikasi dan data profil:

  • dokumen yang menentukan keabsahan anggota tetap ditinjau;
  • profesor memiliki kepemilikan atas profil akademiknya;
  • admin tidak dibebani approval untuk setiap perubahan kecil;
  • direktori dapat terus diperbarui tanpa proses birokrasi tambahan.

Tidak Semua Fitur Perlu Dibangun

Salah satu kebutuhan adalah memungkinkan publik menghubungi profesor sebagai narasumber. Kami bisa saja membuat inbox internal lengkap dengan thread, notifikasi, status pesan, dan moderasi.

Namun, kebutuhan yang sebenarnya adalah membuka jalur kontak, bukan menciptakan platform komunikasi baru. Karena itu, aksi “Undang Narasumber” diarahkan ke email profesor.

Kami kehilangan kemampuan tracking percakapan di dalam sistem, tetapi menghindari pembangunan satu modul besar yang belum terbukti akan digunakan. Ini adalah contoh penting bahwa keputusan produk yang baik tidak selalu berupa penambahan fitur. Kadang keputusan terbaik adalah menetapkan batas dan tidak membangun sesuatu terlalu cepat.

Fitur yang Akhirnya Terbentuk

Platform APMNU mencakup beberapa kemampuan utama:

  • direktori profesor publik dengan pencarian dan filter;
  • registrasi anggota dengan proses review dan revisi;
  • portal self-service untuk profil akademik;
  • kartu anggota digital bagi profesor yang telah disetujui;
  • dashboard admin dengan statistik dan daftar pendaftaran terbaru;
  • pengelolaan agenda dan liputan organisasi;
  • materi akademik, galeri, dan halaman profil organisasi;
  • rich text editor untuk pengelola konten non-teknis;
  • dokumentasi REST API melalui Swagger.

Pada halaman publik, konten agenda dibuat visual dan responsif agar tetap nyaman dilihat dari perangkat mobile.

Tampilan agenda APMNU pada perangkat mobile

Menjalankan Peran PM dan Developer Bersamaan

Dalam proyek ini, saya tidak hanya menerima task implementasi. Saya juga menerjemahkan kebutuhan klien menjadi PRD, menyusun backlog, membagi pekerjaan, menjalankan sprint planning dan weekly review, serta menjaga agar keputusan produk tidak terlepas dari kondisi teknis.

Kami awalnya menggunakan Linear, kemudian memindahkan tracking pekerjaan ke Jira. Migrasi tool di tengah proyek berisiko membuat konteks hilang, sehingga yang perlu dipindahkan bukan hanya judul issue, tetapi juga acceptance criteria, hubungan antartask, keputusan sebelumnya, dan status pekerjaan yang sebenarnya.

Menjalankan peran PM sekaligus developer memberi keuntungan: saya dapat melihat lebih cepat ketika sebuah kebutuhan memiliki konsekuensi besar pada domain atau arsitektur. Namun, ada risiko terlalu fokus pada detail implementasi. PRD, backlog yang rapi, dan ritme review membantu saya tetap menjaga pandangan produk secara keseluruhan.

Pelajaran terbesarnya adalah bahwa dokumentasi bukan pekerjaan setelah coding selesai. Dokumentasi adalah alat untuk membuat tim bergerak dengan pemahaman yang sama.

CI/CD: Rilis yang Bisa Dilacak dan Diulang

Ketiga aplikasi dikemas menjadi image Docker terpisah dan disimpan di GitHub Container Registry. Image diberi tag berdasarkan commit SHA, bukan hanya latest.

Alur sederhananya:

Push / workflow dispatch


Lint + typecheck + build


Build image aplikasi yang berubah


Push ke GHCR dengan tag commit SHA


Deploy ke staging atau production

Tag commit SHA membuat artefak rilis dapat ditelusuri kembali ke source code yang tepat. Jika versi tertentu bermasalah, rollback dapat dilakukan dengan menunjuk image sebelumnya tanpa build ulang di server.

Kami juga menggunakan deteksi perubahan berbasis path. Perubahan yang hanya menyentuh panel admin tidak perlu memicu seluruh proses untuk API dan website publik. Selain menghemat waktu CI, pola ini menjaga pipeline tetap relevan dengan batas aplikasi di dalam monorepo.

Hal yang Paling Sulit

Bagian tersulit bukan memilih Nuxt, NestJS, atau Prisma. Tantangan terbesarnya adalah menjaga agar pemahaman produk tetap konsisten di antara kebutuhan organisasi, model data, API, tampilan untuk tiga jenis pengguna, dan pekerjaan beberapa developer.

Beberapa hal yang paling membantu:

  1. Menyepakati istilah domain lebih awal. Nama yang tepat mengurangi ambiguitas pada database, endpoint, UI, dan diskusi tim.

  2. Menulis alasan di balik keputusan. Dokumentasi arsitektur menjawab “mengapa dibuat seperti ini”, bukan hanya “bagaimana cara menjalankannya”.

  3. Menjadikan schema dan typecheck sebagai quality gate. Kontrak yang rusak perlu ditemukan sebelum deployment.

  4. Mendesain untuk pergantian pengurus. Struktur data, nama menu, dan dokumentasi tidak boleh bergantung pada satu orang yang sedang menjabat.

  5. Membedakan kebutuhan nyata dari solusi yang terlalu besar. Email langsung dapat menjadi keputusan yang lebih baik daripada membangun messaging internal terlalu dini.

Penutup

Proyek APMNU mengajarkan saya bahwa membangun sistem organisasi berarti merancang hubungan antara data, proses verifikasi, pengguna publik, dan operasi internal—bukan sekadar menyusun halaman.

Monorepo membantu kami menjaga kontrak lintas aplikasi. Pemisahan status membuat domain lebih tepat. Portal self-service mengurangi beban pengurus. Pipeline berbasis image membuat rilis dapat diulang dan dilacak. Di atas semua itu, PRD dan dokumentasi menjaga tim tetap menyelesaikan masalah yang sama.

Hasil akhirnya adalah satu platform yang menghubungkan website publik, keanggotaan profesor, dan pekerjaan pengurus tanpa kembali menciptakan silo data baru.

Versi ringkas proyek, diagram arsitektur, stack, dan kontribusi saya dapat dilihat di case study APMNU. Website publiknya tersedia di apmnu.org, sedangkan source code dapat dilihat di GitHub.