Aplikasi Document Management System (DMS) berbasis Fullstack yang terdiri dari:
- Frontend UI (Next.js Web App): login, dashboard, documents (upload & list), request delete/replace, panel admin (create user & approval queue), notifikasi.
- Backend API (NestJS): autentikasi JWT, manajemen dokumen, workflow approval, notifikasi, secure download.
- Database (Prisma ORM): user, documents, permission requests (approvals), notifications.
- File Storage: filesystem lokal (
uploads/) — bisa dikembangkan ke S3/MinIO.
Mode operasional: Opsi A (Admin membuat user)
Tidak ada “register publik”. Pembuatan user dilakukan oleh ADMIN.
- Login menggunakan JWT
- Role-based access
ADMIN: create user, lihat approval queue, approve/reject.USER: upload dokumen, list dokumen miliknya, request delete/replace, menerima notifikasi.
- Rate limit login: 10 request / 3 menit per IP (mengurangi brute force).
- Upload dokumen (multipart/form-data)
- File restriction: hanya PDF/DOCX, max 10MB
- List dokumen + search + pagination
- Replace dokumen (via approval admin) →
fileUrlberubah,versionincrement - Delete dokumen (via approval admin)
- DELETE
- USER request delete → document status
PENDING_DELETE - ADMIN approve → document + file fisik terhapus → user notified
- ADMIN reject → status kembali
ACTIVE
- USER request delete → document status
- REPLACE
- USER request replace → upload file baru → document status
PENDING_REPLACE - ADMIN approve → fileUrl diupdate + version increment + file lama dihapus → user notified
- ADMIN reject → status kembali
ACTIVE+ file replace (baru) dihapus (cleanup)
- USER request replace → upload file baru → document status
- Notifikasi disimpan di DB dan ditampilkan di UI
- Bisa ditingkatkan dengan Outbox + Worker (lihat System Design)
- Folder
uploads/tidak di-serve publik - Download file dilakukan via endpoint protected (JWT + authorization):
GET /documents/:id/download
- Swagger UI:
http://localhost:3000/docs
Backend
- NestJS
- Prisma ORM
- PostgreSQL
- JWT Auth
- Multer (file upload)
- Throttler (rate limit)
Frontend
- Next.js (App Router)
- Fetch API
- UI: Login, Dashboard, Documents, Users (Admin), Approvals (Admin), Notifications
dms-fullstack-cybermax-v6/
├─ dms-backend/
│ ├─ src/
│ ├─ prisma/
│ ├─ docker-compose.yml
│ └─ ...
└─ dms-frontend/
├─ src/
└─ ...
- Node.js (LTS disarankan)
- NPM
- PostgreSQL (atau Docker)
- Docker (opsional)
Buat file: dms-backend/.env
PORT=3000
JWT_SECRET=supersecret
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/dms?schema=public"
# Support multiple origin via koma
CORS_ORIGIN="http://localhost:3001,http://localhost:3000"Folder
uploads/akan otomatis dibuat saat server start.
Buat file: dms-frontend/.env.local
NEXT_PUBLIC_API_BASE_URL=http://localhost:3000cd dms-backend
npm install
npx prisma migrate dev
npx prisma db seed
npm run start:devSwagger:
http://localhost:3000/docs
cd dms-frontend
npm install
npm run devUI:
http://localhost:3001
Karena tidak ada register publik, admin pertama dibuat via seed.
Contoh kredensial (sesuaikan dengan dms-backend/prisma/seed.ts):
- Email:
admin@mail.com - Password:
Admin123!
Setelah login sebagai admin, pembuatan user dilakukan lewat:
- UI: menu
Users (Admin)→/users, atau - Endpoint
POST /users(ADMIN only)
- Jalankan DB + Backend
- Jalankan Frontend
- Login sebagai ADMIN (akun seed)
- Buat akun USER dari menu Users (Admin)
- Login sebagai USER → upload dokumen
- USER request delete/replace
- ADMIN buka Approvals → approve/reject
- USER melihat notifikasi + status dokumen berubah
- Download dokumen via tombol Download (Secure)
Detail resmi lihat Swagger:
http://localhost:3000/docs
POST /auth/login→ login & mendapatkan JWT (rate-limited)GET /auth/me→ payload user saat ini (JWT)
POST /users→ create user
GET /documents?q=&page=&limit=→ list + search + paginationGET /documents/:id→ detail docPOST /documents→ upload dokumen (multipart, PDF/DOCX only, max 10MB)GET /documents/:id/download→ secure download (JWT + authorization)POST /documents/:id/request-delete→ request delete (owner/admin)POST /documents/:id/request-replace→ request replace (multipart, PDF/DOCX only)
GET /approvals/requests→ list request PENDING (admin only)POST /approvals/requests/:id/approve→ approve request (DELETE/REPLACE)POST /approvals/requests/:id/reject→ reject request (DELETE/REPLACE)
GET /notifications→ list my notificationsGET /notifications/me→ list my notifications (alias)POST /notifications/:id/read→ mark as read
USER bisa:
- Login
- Upload dokumen
- List dokumen miliknya
- Membuat request DELETE/REPLACE (dokumen lock ke status PENDING)
- Menerima & membaca notifikasi
- Download dokumen miliknya via endpoint secure
USER tidak bisa:
- Approve/reject request
- Melihat approval queue semua user
ADMIN bisa:
- Membuat user baru (Opsi A)
- Melihat semua request PENDING
- Approve/reject
- Download semua dokumen (secure download)
- Aksi sensitif (delete/replace) dieksekusi oleh backend saat approve
flowchart LR
classDef actor fill:#2b2b2b,stroke:#999,color:#fff;
classDef process fill:#1f2937,stroke:#94a3b8,color:#fff;
classDef db fill:#2a1f3d,stroke:#a78bfa,color:#fff;
Dev[Developer]:::actor -->|Run seed| Seed[Prisma Seed]:::process
Seed -->|Upsert ADMIN| DB[(Database)]:::db
DB --> Admin[ADMIN ready]:::actor
flowchart LR
classDef actor fill:#2b2b2b,stroke:#999,color:#fff;
classDef ui fill:#3a2a12,stroke:#f59e0b,color:#fff;
classDef api fill:#0b3a53,stroke:#38bdf8,color:#fff;
classDef db fill:#2a1f3d,stroke:#a78bfa,color:#fff;
A["ADMIN"]:::actor --> UI["Admin Panel"]:::ui
UI --> R1["POST /users (ADMIN only)"]:::api
R1 --> API["Backend API"]:::api
API --> DB[(Database)]:::db
DB --> API
API --> UI
flowchart LR
classDef actor fill:#2b2b2b,stroke:#999,color:#fff;
classDef ui fill:#3a2a12,stroke:#f59e0b,color:#fff;
classDef api fill:#0b3a53,stroke:#38bdf8,color:#fff;
classDef store fill:#0f2f2a,stroke:#34d399,color:#fff;
classDef db fill:#2a1f3d,stroke:#a78bfa,color:#fff;
U["USER"]:::actor --> UI["Documents Page"]:::ui
UI --> R1["POST /documents (multipart + JWT)"]:::api
R1 --> API["Backend API"]:::api
API --> V["Validate type and size"]:::api
V --> FS[(uploads)]:::store
API --> DB[(Database)]:::db
DB --> API
API --> UI
flowchart LR
classDef actor fill:#2b2b2b,stroke:#999,color:#fff;
classDef ui fill:#3a2a12,stroke:#f59e0b,color:#fff;
classDef api fill:#0b3a53,stroke:#38bdf8,color:#fff;
classDef store fill:#0f2f2a,stroke:#34d399,color:#fff;
classDef db fill:#2a1f3d,stroke:#a78bfa,color:#fff;
U[USER]:::actor --> UI[UI]:::ui
UI -->|POST /documents/:id/request-delete| API[Backend API]:::api
API --> DB[(Database)]:::db
A[ADMIN]:::actor --> UI
UI -->|POST /approvals/requests/:id/approve| API
API --> FS[(uploads/)]:::store
API --> DB
flowchart LR
classDef actor fill:#2b2b2b,stroke:#999,color:#fff;
classDef ui fill:#3a2a12,stroke:#f59e0b,color:#fff;
classDef api fill:#0b3a53,stroke:#38bdf8,color:#fff;
classDef store fill:#0f2f2a,stroke:#34d399,color:#fff;
classDef db fill:#2a1f3d,stroke:#a78bfa,color:#fff;
U["USER"]:::actor --> UI["UI"]:::ui
UI --> R1["POST /documents/:id/request-replace (multipart)"]:::api
R1 --> API["Backend API"]:::api
API --> FS[(uploads)]:::store
API --> DB[(Database)]:::db
A["ADMIN"]:::actor --> UI
UI --> R2["POST /approvals/requests/:id/approve"]:::api
R2 --> API
API --> C["Update document and cleanup old file"]:::api
C --> FS
API --> DB
- Disk storage + limit ukuran + validasi mime-type
- Chunk/resumable upload (opsional)
- Object storage + pre-signed URL (S3/MinIO)
- Simpan metadata (ukuran, hash/checksum) + (opsional) antivirus scanning
- Batasi 1 request PENDING per dokumen
- Transaction saat approve: update document + approval + notification atomik
- Optimistic concurrency (kolom
version/updatedAt) → 409 jika konflik
- Mulai dari in-app notification di DB
- Outbox pattern + worker
- Message broker (RabbitMQ/Kafka/Redis Streams)
- Idempotency (eventId)
- Jangan expose folder uploads publik tanpa auth
- Download via endpoint terproteksi (JWT + authorization)
- Jika object storage: pre-signed URL TTL pendek
- Validasi upload (type/size), rate limit, audit log
- Modular monolith: Auth, Users, Documents, Approvals, Notifications
- Domain events (DocumentReplaced, ApprovalApproved, dst.)
- Kandidat microservice pertama: Notifications → Approvals → Documents
- Pastikan backend berjalan
- Buka
http://localhost:3000/docs
- Pastikan
CORS_ORIGINmengandung origin frontend - Restart backend setelah ubah
.env
npx prisma generate
npx prisma migrate devIchsan
https://github.com/ichsanx/dms-fullstack-cybermax-v6
MIT License — lihat file LICENSE.
Copyright (c) 2026 Ichsan