Setup #

Keputusan paling mahal dalam web application bukan keputusan yang dibuat saat sistem sudah besar dan kompleks — melainkan keputusan yang diabaikan di hari-hari pertama pengembangan. Security header yang tidak dipasang dari awal, database connection pool yang tidak dikonfigurasi, graceful shutdown yang tidak diimplementasikan — semua ini adalah hutang teknikal yang tidak terlihat sampai sistem mulai gagal di production. Artikel ini membahas fondasi-fondasi yang perlu ada sebelum baris kode bisnis pertama ditulis: dari HTTP layer sampai deployment safety, dari security sampai observability. Bukan pilihan framework atau arsitektur — melainkan prinsip-prinsip setup yang berlaku untuk semua stack.

Mengapa Setup Awal Penting #

Biaya memperbaiki masalah struktural di aplikasi yang sudah berjalan jauh lebih tinggi dari biaya melakukannya dari awal. Beberapa contoh konkret:

Masalah yang mahal jika diperbaiki belakangan:

1. Security headers tidak ada
   Early: tambahkan 10 baris config → selesai
   Later: audit seluruh response, test regresi, koordinasi dengan CDN → berminggu-minggu

2. Database connection pool tidak dikonfigurasi
   Early: 3 baris config saat setup
   Later: diagnose connection leak di production, incident downtime, trace root cause → berhari-hari

3. Tidak ada graceful shutdown
   Early: implementasi di main() saat pertama kali
   Later: request yang in-flight terpotong saat deploy, data corruption, user complaint → patch darurat

4. CORS wildcard (*)
   Early: whitelist domain saat config pertama
   Later: cross-origin credential abuse sudah terjadi, audit damage, revamp auth → incident

Prinsipnya: setiap item di artikel ini butuh 30 menit–2 jam jika dilakukan saat setup,
dan bisa butuh berhari-hari jika dilakukan setelah production mengalami masalah.

HTTP dan Network Layer #

Response Compression #

Compression mengurangi ukuran response yang dikirim ke client. Untuk response berbasis teks (HTML, JSON, CSS, JavaScript), compression bisa mengurangi ukuran 60–80%.

Yang harus dicompress:
  ✓ HTML, CSS, JavaScript
  ✓ JSON response dari API
  ✓ SVG dan XML
  ✓ Plain text

Yang tidak boleh dicompress:
  ✗ Image (JPEG, PNG, WebP sudah terkompresi — compress lagi hanya buang CPU)
  ✗ Video dan audio
  ✗ File ZIP, PDF, dan binary yang sudah terkompresi

Pilihan algoritma:
  Gzip:   Didukung semua browser sejak lama. Default yang aman.
  Brotli: Kompresi lebih baik (~20% lebih kecil dari Gzip), didukung browser modern.
          Wajib HTTPS. Gunakan Brotli jika aplikasi sudah full HTTPS.

Cara check apakah compression sudah aktif:
  curl -H "Accept-Encoding: gzip" -I https://example.com/api/data
  → Harus ada: Content-Encoding: gzip di response headers

Contoh konfigurasi Nginx:
  gzip on;
  gzip_types text/plain application/json text/css application/javascript;
  gzip_min_length 1000;  # jangan compress response yang sangat kecil

HTTP Security Headers #

Banyak serangan web — clickjacking, MIME sniffing, XSS, downgrade attack — bisa dicegah hanya dengan mengset header yang benar. Ini adalah lapisan keamanan yang paling murah dan paling sering diabaikan.

Header wajib untuk semua web application:

Strict-Transport-Security: max-age=31536000; includeSubDomains
  → Paksa HTTPS. Browser tidak akan pernah kirim request via HTTP setelah ini.
  → Tambahkan preload jika siap didaftarkan ke HSTS preload list.

X-Content-Type-Options: nosniff
  → Cegah browser "menebak" tipe konten.
  → Jika server bilang ini application/json, browser tidak akan coba interpretasi lain.

X-Frame-Options: DENY
  → Cegah halaman ini dimuat di dalam iframe.
  → Melindungi dari clickjacking attack.
  → Gunakan SAMEORIGIN jika membutuhkan iframe dari domain yang sama.

Referrer-Policy: strict-origin-when-cross-origin
  → Batasi informasi Referer yang dikirim ke domain lain.
  → Cegah URL sensitif (yang mungkin mengandung token) bocor ke third-party.

Content-Security-Policy: default-src 'self'
  → Batasi sumber resource yang boleh dimuat halaman ini.
  → Mulai dari kebijakan ketat, relaxing secara gradual berdasarkan kebutuhan.
  → Mencegah injeksi script dari sumber eksternal.

Permissions-Policy: camera=(), microphone=(), geolocation=()
  → Nonaktifkan fitur browser yang tidak dibutuhkan aplikasi.
  → Mencegah malicious script memanfaatkan permission yang tidak perlu.
// Cara implementasi di middleware (contoh Go):
func securityHeadersMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
        w.Header().Set("X-Content-Type-Options", "nosniff")
        w.Header().Set("X-Frame-Options", "DENY")
        w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
        w.Header().Set("Content-Security-Policy", "default-src 'self'")
        w.Header().Set("Permissions-Policy", "camera=(), microphone=()")
        next.ServeHTTP(w, r)
    })
}
Gunakan tool seperti securityheaders.com untuk mengaudit header yang sudah dipasang. Tool ini memberikan grade A–F beserta rekomendasi spesifik. Target minimal adalah grade B sebelum go-live, grade A untuk aplikasi yang menangani data sensitif.

Security Layer #

CSRF Protection #

CSRF (Cross-Site Request Forgery) adalah serangan di mana situs jahat memaksa browser user untuk mengirim request ke aplikasimu tanpa sepengetahuan user. Karena browser otomatis menyertakan cookie, request tersebut terlihat sah dari sisi server.

sequenceDiagram
    participant U as User
    participant B as Browser
    participant M as Malicious Site
    participant A as Your App

    Note over U,A: Tanpa CSRF protection

    U->>A: Login ke aplikasi → session cookie disimpan
    U->>M: Mengunjungi situs jahat (link di email, dll)
    M->>B: Halaman berisi: <img src="https://yourapp.com/transfer?to=attacker&amount=1000000">
    B->>A: GET /transfer?to=attacker&amount=1000000 [dengan session cookie otomatis!]
    A->>A: Cookie valid → transfer dieksekusi
    Note over A: Uang ditransfer tanpa user sadari

    Note over U,A: Dengan CSRF protection
    A-->>B: Setiap form berisi CSRF token unik
    M->>B: Halaman jahat tidak punya CSRF token yang valid
    B->>A: Request tanpa CSRF token → ditolak 403
Kapan CSRF protection wajib:
  ✓ Aplikasi web yang menggunakan session-based authentication (cookie)
  ✓ Endpoint yang mengubah state (POST, PUT, PATCH, DELETE)

Kapan CSRF protection tidak diperlukan:
  ✗ API stateless yang menggunakan Bearer Token (bukan cookie)
     → Token tidak otomatis dikirim browser, sehingga serangan CSRF tidak applicable
  ✗ Endpoint hanya GET (read-only, tidak ada side effect)

Implementasi CSRF token:
  1. Generate random token per session (atau per request untuk lebih aman)
  2. Simpan token di session
  3. Sertakan token di setiap form sebagai hidden field
  4. Sertakan token di AJAX request sebagai header (X-CSRF-Token)
  5. Server validasi token di setiap request non-GET

Cookie configuration untuk CSRF defense:
  SameSite=Lax   → Cookie tidak dikirim di cross-site request dari link eksternal
  SameSite=Strict → Cookie tidak dikirim di semua cross-site request
  HttpOnly        → Cookie tidak bisa diakses JavaScript
  Secure          → Cookie hanya dikirim via HTTPS

Authentication dan Session Management #

Kesalahan di session management adalah salah satu entry point serangan yang paling umum.

Session-based authentication:
  ✓ Regenerate session ID setelah login (session fixation prevention)
  ✓ Set idle timeout (session tidak aktif → invalidasi otomatis)
  ✓ Simpan session di Redis atau database, BUKAN memory lokal
     (memory lokal tidak survive restart, tidak works dengan multiple instance)
  ✓ Set session cookie: HttpOnly + Secure + SameSite
  ✓ Logout harus invalidasi session di server, bukan hanya hapus cookie

JWT-based authentication:
  ✓ Access token berumur pendek (5–15 menit)
  ✓ Refresh token disimpan di HttpOnly Secure cookie
  ✓ JANGAN simpan JWT di localStorage (rentan XSS)
  ✓ Validasi signature, exp, iss, aud di setiap request

// ANTI-PATTERN: Session di memory lokal
var sessions = map[string]Session{}  // hilang saat restart, tidak scalable

// BENAR: Session di Redis
func (s *SessionStore) Get(id string) (*Session, error) {
    data, err := s.redis.Get(ctx, "session:"+id).Bytes()
    // ...
}

Rate Limiting #

Rate limiting bukan hanya untuk performa — ini adalah defense terhadap brute force, credential stuffing, dan abuse.

Endpoint yang wajib punya rate limiting lebih ketat:
  POST /auth/login          → 5 request/menit per IP
  POST /auth/register       → 3 request/menit per IP
  POST /auth/forgot-password → 3 request/menit per email
  POST /auth/verify-otp     → 3 request/menit per user

Endpoint yang butuh rate limiting standar:
  Semua API endpoint → 100–1000 request/menit per user/IP

Response yang benar saat rate limited:
  HTTP 429 Too Many Requests
  Retry-After: 47  (berapa detik lagi boleh coba)
  X-RateLimit-Limit: 5
  X-RateLimit-Remaining: 0
  X-RateLimit-Reset: 1706356047

Asset dan Static Content #

Asset Versioning dan Caching #

Tanpa asset versioning, user mungkin menggunakan CSS atau JavaScript lama setelah deployment karena cache browser.

// ANTI-PATTERN: Asset tanpa versioning
<link href="/styles.css" rel="stylesheet">
<script src="/app.js"></script>
→ Setelah deploy, browser masih pakai versi lama dari cache

// BENAR: Asset dengan content hash
<link href="/styles.4f3a2c1b.css" rel="stylesheet">
<script src="/app.8d2e5f9a.js"></script>
→ Hash berubah saat konten berubah → cache invalidated otomatis
→ Hash sama jika konten tidak berubah → cache tetap valid

Cache-Control untuk asset dengan hash:
  Cache-Control: public, max-age=31536000, immutable
  → Cache 1 tahun, immutable (browser tidak perlu revalidate)
  → Aman karena URL pasti berubah jika konten berubah

Cache-Control untuk HTML (yang mereferensikan asset):
  Cache-Control: no-cache
  → Browser revalidate setiap kali, tapi bisa pakai cache jika ETag sama
  → Pastikan HTML terbaru selalu diload agar referensi ke asset hash terbaru

CORS Configuration #

CORS yang terlalu permissive adalah security issue yang sering diremehkan.

// ANTI-PATTERN: Wildcard dengan credentials (kombinasi yang berbahaya)
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
// Ini tidak valid sesuai spec — browser akan menolak
// Tapi beberapa implementasi server tidak konsisten

// ANTI-PATTERN: Wildcard tanpa credentials (masih terlalu permissive)
Access-Control-Allow-Origin: *
// Membolehkan request dari domain apapun — termasuk situs jahat

// BENAR: Whitelist origin eksplisit
var allowedOrigins = []string{
    "https://app.example.com",
    "https://admin.example.com",
}

func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        origin := r.Header.Get("Origin")
        for _, allowed := range allowedOrigins {
            if origin == allowed {
                w.Header().Set("Access-Control-Allow-Origin", origin)
                w.Header().Set("Vary", "Origin")  // penting untuk caching
                break
            }
        }
        // ...
    })
}

// Batasi method dan header yang diizinkan:
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-ID
Access-Control-Max-Age: 3600  // cache preflight response 1 jam

Performance Baseline #

Database Connection Pool #

Ini adalah konfigurasi yang paling sering diabaikan dan paling sering menyebabkan incident production.

Masalah tanpa connection pool:
  Setiap request membuka koneksi baru ke database
  → Koneksi lambat (~5–50ms untuk establish)
  → Database punya batas koneksi (misal: PostgreSQL default 100)
  → Load spike = koneksi habis = seluruh sistem tidak responsif

Masalah dengan pool yang tidak dikonfigurasi dengan benar:
  Connection leak: koneksi tidak pernah dikembalikan ke pool
  → Pool penuh → request menunggu → timeout → error massal

Konfigurasi yang perlu diset:
  MaxOpenConns     = batas maksimum koneksi aktif ke database
  MaxIdleConns     = batas koneksi idle yang disimpan di pool
  ConnMaxLifetime  = batas waktu hidup sebuah koneksi (cegah stale connection)
  ConnMaxIdleTime  = batas waktu koneksi idle sebelum ditutup

Panduan nilai awal (sesuaikan berdasarkan load):
  MaxOpenConns:    25 per instance (jika 4 instance → 100 koneksi ke DB)
  MaxIdleConns:    10
  ConnMaxLifetime: 5 menit
  ConnMaxIdleTime: 1 menit

// Contoh Go:
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(1 * time.Minute)

Layered Caching Strategy #

flowchart LR
    Client["Browser / Mobile"]
    CDN["CDN Cache\n(Edge)"]
    AppCache["Application Cache\n(Redis)"]
    DB["Database"]

    Client -->|"Request"| CDN
    CDN -->|"Cache miss"| AppCache
    AppCache -->|"Cache miss"| DB
    DB -->|"Data"| AppCache
    AppCache -->|"Cache hit"| CDN
    CDN -->|"Cache hit"| Client

    style CDN fill:#27AE60,color:#fff
    style AppCache fill:#E67E22,color:#fff
    style DB fill:#8E44AD,color:#fff
Layer caching dan apa yang cocok untuk masing-masing:

Browser Cache (Cache-Control header):
  → Static asset dengan content hash: max-age=31536000, immutable
  → HTML: no-cache (revalidate setiap kali)
  → API response yang jarang berubah: max-age=300

CDN Cache:
  → Semua static asset (JavaScript, CSS, gambar)
  → API response yang tidak user-specific
  → Konfigurasi: cache asset paling agresif, API response lebih konservatif

Application Cache (Redis):
  → Config dan feature flags
  → Session data
  → Hasil query yang mahal dan read-heavy
  → Rate limit counters
  TTL harus disesuaikan dengan seberapa sering data berubah

Error Handling dan Observability #

Error Response yang Konsisten #

// ANTI-PATTERN: Error yang membocorkan detail internal
HTTP 500
{
  "error": "pq: duplicate key value violates unique constraint \"users_email_key\"",
  "sql": "INSERT INTO users (email, ...) VALUES ($1, ...)"
}
// Attacker tahu: database adalah PostgreSQL, nama tabel, nama constraint

// BENAR: Error yang informatif tapi aman
HTTP 409 Conflict
{
  "error": {
    "code": "EMAIL_ALREADY_REGISTERED",
    "message": "Email ini sudah terdaftar. Gunakan fitur forgot password jika lupa sandi."
  }
}
// User mendapat informasi yang actionable, tanpa detail internal

Structured Logging #

// ANTI-PATTERN: Log tidak terstruktur
log.Println("Request processed: user 123, endpoint /orders, 234ms, status 200")
// Sulit di-parse, sulit di-query, sulit di-aggregate

// BENAR: Structured JSON logging
{
  "timestamp": "2024-01-27T14:32:01.123Z",
  "level": "INFO",
  "request_id": "req_abc123def456",
  "user_id": "usr_789",
  "method": "GET",
  "path": "/api/orders",
  "status": 200,
  "duration_ms": 234,
  "ip": "203.0.113.1"
}
// Bisa di-query, di-filter, di-aggregate, dan di-alert otomatis

Log fields yang wajib ada di setiap request log: timestamp, request_id, method, path, status, duration_ms. Log fields yang wajib ada untuk request authenticated: tambahkan user_id. Jangan pernah log: password, token, credit card, PII yang tidak diperlukan.

Health Check dan Readiness #

Dua endpoint yang berbeda tujuannya:

GET /health (liveness check):
  → "Apakah proses ini masih hidup?"
  → Jika tidak respond → container/process di-restart
  → Harus sangat cepat, tidak boleh check dependency (DB, Redis)
  → Return 200 OK jika proses berjalan, apapun kondisi dependency

GET /ready (readiness check):
  → "Apakah service ini siap menerima traffic?"
  → Jika tidak respond → traffic dialihkan ke instance lain (tidak di-kill)
  → Boleh check: apakah DB bisa diquery? Apakah Redis bisa di-ping?
  → Return 200 jika semua dependency ready, 503 jika tidak

// Contoh response readiness yang informatif:
HTTP 200 OK
{
  "status": "ready",
  "checks": {
    "database": "ok",
    "redis": "ok",
    "disk_space": "ok"
  },
  "version": "v1.42.0"
}

// Contoh response saat tidak ready:
HTTP 503 Service Unavailable
{
  "status": "not_ready",
  "checks": {
    "database": "ok",
    "redis": "connection_refused",  ← ini yang bermasalah
    "disk_space": "ok"
  }
}

Configuration dan Environment #

Secrets Management #

Hierarki configuration yang baik:

Hardcode di kode:          JANGAN PERNAH untuk credential dan secret
.env file di repo:         JANGAN PERNAH (kecuali template tanpa nilai nyata)
Environment variable:      ✓ Untuk deployment, container, CI/CD
Config file (non-secret):  ✓ Untuk nilai non-sensitif yang perlu versioning
Secrets manager:           ✓ Untuk production (AWS Secrets Manager, Vault, dll)

// ANTI-PATTERN: Hardcode credential
const (
    DBPassword = "supersecret123"
    JWTSecret  = "myjwtsecret"
    APIKey     = "sk_live_abcdef123456"
)

// BENAR: Dari environment variable
dbPassword := os.Getenv("DB_PASSWORD")
if dbPassword == "" {
    log.Fatal("DB_PASSWORD environment variable is required")
}

// Fail fast jika required config tidak ada — jangan biarkan aplikasi
// start dengan nilai kosong yang mungkin menyebabkan masalah lebih lanjut

Environment Separation #

Tiga environment yang perlu ada:

Development (local):
  → Database lokal, tidak ada data user nyata
  → Error detail boleh verbose
  → Tidak butuh HTTPS
  → Mock third-party service jika perlu

Staging:
  → Konfigurasi semirip mungkin dengan production
  → Data sintetis atau anonymized data
  → HTTPS
  → Koneksi ke third-party staging/sandbox (bukan production)
  → Tempat testing sebelum deploy ke production

Production:
  → Semua config diload dari secrets manager atau environment variable
  → Error response minimal (tidak ada detail teknis)
  → Semua feature security diaktifkan
  → Monitoring dan alerting aktif

Bahaya utama tanpa environment separation:
  → Developer testing langsung di production database
  → Credential production bocor ke developer environment
  → Testing yang tidak sengaja menghapus atau merusak data production

Deployment dan Runtime Safety #

Graceful Shutdown #

Graceful shutdown memastikan bahwa saat deployment atau restart, request yang sedang diproses selesai dulu sebelum proses ditutup.

// Implementasi graceful shutdown di Go
func main() {
    server := &http.Server{
        Addr:    ":8080",
        Handler: setupRouter(),
    }

    // Channel untuk menangkap signal dari OS
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    // Jalankan server di goroutine terpisah
    go func() {
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    log.Println("Server started on :8080")

    // Block sampai signal diterima
    <-quit
    log.Println("Shutting down server...")

    // Beri waktu 30 detik untuk request aktif selesai
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }

    log.Println("Server exited properly")
}

Timeout di Semua Level #

Timeout yang perlu dikonfigurasi:

HTTP server:
  ReadTimeout:       5s   (batas waktu baca request dari client)
  WriteTimeout:      15s  (batas waktu tulis response ke client)
  IdleTimeout:       60s  (batas waktu koneksi idle di-keep-alive)
  ReadHeaderTimeout: 2s   (batas waktu baca header saja)

Database query:
  Per query: 5s (query lebih dari 5 detik → ada yang salah)
  Konfigurasi di context: ctx, cancel := context.WithTimeout(ctx, 5*time.Second)

External API call:
  HTTP client timeout: sesuaikan dengan SLA third-party
  Minimum: 3–10 detik, maksimum sesuai kebutuhan

Kenapa timeout penting:
  → Tanpa timeout, satu request lambat bisa memblokir goroutine/thread selamanya
  → Akumulasi goroutine yang menunggu → memory habis → OOM
  → Sistem kolaps karena satu dependency lambat

// Contoh konfigurasi server dengan semua timeout:
server := &http.Server{
    Addr:              ":8080",
    Handler:           handler,
    ReadTimeout:       5 * time.Second,
    WriteTimeout:      15 * time.Second,
    IdleTimeout:       60 * time.Second,
    ReadHeaderTimeout: 2 * time.Second,
}
Timeout yang tidak dikonfigurasi adalah bom waktu. Satu query database yang tidak ada timeout-nya bisa menyebabkan goroutine menunggu selamanya, mengakumulasi sampai memory habis, dan menyebabkan seluruh service down — karena satu query yang bermasalah. Konfigurasi timeout di semua level: server, database, dan external API call.

Checklist Setup Awal #

HTTP DAN NETWORK:
  □ Response compression aktif (gzip atau brotli)
  □ HTTP security headers lengkap (HSTS, X-Content-Type-Options, X-Frame-Options, dll)
  □ HTTPS di semua environment (development boleh exception)

SECURITY:
  □ CSRF protection aktif (jika session-based auth)
  □ Session: HttpOnly + Secure + SameSite cookie
  □ Session disimpan di Redis/DB, bukan memory lokal
  □ Rate limiting di endpoint autentikasi
  □ CORS whitelist explicit, bukan wildcard

ASSET DAN STATIC:
  □ Asset versioning dengan content hash
  □ Static asset di CDN atau object storage
  □ Cache-Control header sesuai untuk setiap jenis konten

PERFORMANCE:
  □ Database connection pool dikonfigurasi (max open, max idle, lifetime)
  □ Caching strategy didefinisikan per jenis data

ERROR DAN OBSERVABILITY:
  □ Error response ke client generik (tidak ada detail internal)
  □ Structured JSON logging dengan request_id
  □ /health endpoint (liveness check, tidak check dependency)
  □ /ready endpoint (readiness check, check dependency)
  □ Metrics: request count, latency histogram, error rate

CONFIGURATION:
  □ Credential dan secret di environment variable, bukan hardcode
  □ Aplikasi fail fast jika required config tidak ada
  □ Environment terpisah: development, staging, production

DEPLOYMENT:
  □ Graceful shutdown diimplementasikan
  □ Timeout dikonfigurasi: server, DB, external API
  □ Resource limit dikonfigurasi (memory, CPU untuk container)

Ringkasan #

  • Setup awal adalah investasi, bukan overhead — setiap item di checklist ini butuh 30 menit saat setup dan bisa butuh berhari-hari jika diperbaiki setelah production mengalami masalah.
  • HTTP security headers adalah lapisan keamanan gratis — satu middleware dengan 6–8 baris konfigurasi mencegah clickjacking, MIME sniffing, XSS dari CDN injection, dan downgrade attack.
  • CSRF wajib untuk session-based auth, tidak diperlukan untuk Bearer Token — pahami perbedaannya agar tidak salah mengimplementasikan atau melewatkan proteksi yang seharusnya ada.
  • Database connection pool adalah fondasi reliability — tanpa konfigurasi yang benar, load spike sekecil apapun bisa menghabiskan koneksi database dan membuat seluruh sistem tidak responsif.
  • Timeout di semua level adalah pertahanan cascade failure — satu dependency yang lambat tanpa timeout bisa menyebabkan goroutine/thread menumpuk dan crash seluruh service.
  • Graceful shutdown wajib untuk zero-downtime deployment — tanpa ini, setiap deployment berpotensi memotong request yang sedang diproses dan menyebabkan error di sisi user.
  • Structured logging adalah fondasi debuggability — log yang tidak terstruktur tidak bisa di-query, tidak bisa di-alert, dan menjadi tumpukan teks yang tidak berguna saat incident.
  • Asset versioning dengan content hash menyelesaikan masalah cache invalidation — hash berubah saat konten berubah, memungkinkan cache agresif tanpa risiko user mendapat asset lama.
  • CORS whitelist, bukan wildcard — wildcard yang dikombinasikan dengan cookies atau credentials adalah security issue yang sering diabaikan sampai ada penyalahgunaan.
  • Secrets tidak boleh ada di kode atau repo — gunakan environment variable atau secrets manager. Aplikasi harus fail fast jika required config tidak ada, bukan berjalan dengan nilai kosong.

← Sebelumnya: API Security   Berikutnya: Validation →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact