Node.js App

Node.js App #

Caddy sangat cocok sebagai reverse proxy untuk aplikasi Node.js. Caddy menangani TLS, HTTP/2, dan HTTPS otomatis sementara aplikasi Node.js fokus pada logika bisnis. Ini adalah salah satu kombinasi yang paling populer untuk deployment modern.

Arsitektur Deployment #

Internet
    │ HTTPS (443)
    ▼
  Caddy
    │ HTTP (internal)
    ▼
Node.js App (port 3000)
    │
    ▼
Database / Services

Konfigurasi Dasar #

example.com {
    reverse_proxy localhost:3000
}

Hanya satu baris — Caddy otomatis handle HTTPS, HTTP/2, sertifikat TLS, dan redirect HTTP ke HTTPS.


Konfigurasi Lengkap dengan Best Practices #

example.com {
    # Logging akses
    log {
        output file /var/log/caddy/nodejs-access.log {
            roll_size 100mb
            roll_keep 7
            roll_keep_days 30
        }
        format json
    }
    
    # Kompresi response
    encode gzip zstd
    
    # Security headers
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "strict-origin-when-cross-origin"
        -Server
        -X-Powered-By
    }
    
    # Sajikan aset statis langsung dari Caddy (lebih efisien)
    @static path /static/* /favicon.ico /robots.txt
    handle @static {
        root * /var/www/myapp
        file_server {
            # Cache lama untuk aset dengan hash di nama file
            pass_thru
        }
        header Cache-Control "public, max-age=31536000, immutable"
    }
    
    # Semua request lain ke Node.js
    handle {
        reverse_proxy localhost:3000 {
            # Forward IP asli client
            header_up X-Real-IP {remote_host}
            header_up X-Forwarded-For {remote_host}
            header_up X-Forwarded-Proto {scheme}
            header_up Host {host}
            
            # Health check untuk memastikan backend hidup
            health_uri /health
            health_interval 10s
            health_timeout 5s
            health_status 200
            
            # Timeout settings
            transport http {
                dial_timeout 5s
                response_header_timeout 60s
                read_timeout 120s
                write_timeout 120s
            }
        }
    }
}

Node.js dengan PM2 #

PM2 adalah process manager yang menjaga Node.js tetap berjalan dan restart otomatis:

# Install PM2
npm install -g pm2

# Jalankan aplikasi
pm2 start app.js --name myapp

# Atau dengan ecosystem file
// ecosystem.config.js
module.exports = {
    apps: [{
        name: 'myapp',
        script: 'dist/server.js',
        instances: 'max',         // Gunakan semua CPU core
        exec_mode: 'cluster',     // Cluster mode untuk load balancing
        
        env: {
            NODE_ENV: 'development',
            PORT: 3000
        },
        env_production: {
            NODE_ENV: 'production',
            PORT: 3000,
            DB_URL: process.env.DB_URL
        },
        
        // Logging
        error_file: '/var/log/myapp/error.log',
        out_file: '/var/log/myapp/access.log',
        log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
        
        // Restart policy
        max_restarts: 10,
        min_uptime: '10s',
        
        // Memory limit — restart jika melebihi
        max_memory_restart: '500M'
    }]
};
pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup  # Auto-start setelah reboot

Multi-Instance Load Balancing #

example.com {
    encode gzip zstd
    
    reverse_proxy {
        # Multiple Node.js instances untuk load balancing
        to localhost:3000 localhost:3001 localhost:3002 localhost:3003
        
        lb_policy round_robin
        
        health_uri /health
        health_interval 10s
        
        # Passive health — tandai down setelah 3 kali gagal
        fail_duration 30s
        max_fails 3
        
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }
}

Node.js dengan WebSocket #

Express + Socket.io atau similar memerlukan upgrade koneksi ke WebSocket:

example.com {
    # Regular HTTP requests
    handle /api/* {
        reverse_proxy localhost:3000
    }
    
    # WebSocket connections
    handle /socket.io/* {
        reverse_proxy localhost:3000 {
            # Caddy otomatis handle WebSocket upgrade
            # Tidak perlu konfigurasi khusus!
        }
    }
    
    # Static files
    handle {
        root * /var/www/myapp/public
        file_server
    }
}

Zero-Downtime Deployment dengan Node.js #

#!/bin/bash
# deploy-nodejs.sh

APP_DIR="/var/www/myapp"
CADDY_API="http://localhost:2019"

echo "=== Zero-Downtime Node.js Deployment ==="

# 1. Pull kode terbaru
cd "$APP_DIR"
git pull origin main

# 2. Install dependencies
npm ci --production

# 3. Build (jika TypeScript atau bundler)
npm run build

# 4. Reload PM2 dengan zero-downtime
# PM2 cluster mode melakukan rolling restart
pm2 reload ecosystem.config.js --env production

# 5. Tunggu sampai semua instance healthy
sleep 5

# 6. Verifikasi
APP_STATUS=$(pm2 list | grep myapp | grep online)
if [ -z "$APP_STATUS" ]; then
    echo "✗ App tidak berjalan!"
    exit 1
fi

# 7. Health check via Caddy
HTTP_STATUS=$(curl -sf -o /dev/null -w "%{http_code}" https://example.com/health)
if [ "$HTTP_STATUS" != "200" ]; then
    echo "✗ Health check gagal! Status: $HTTP_STATUS"
    exit 1
fi

echo "✓ Deployment berhasil!"

Express.js — Konfigurasi yang Diperlukan #

// app.js — setting yang penting untuk deployment di belakang proxy
const express = require('express');
const app = express();

// PENTING: Trust proxy header dari Caddy
// Ini memungkinkan req.ip mengembalikan IP asli client
app.set('trust proxy', 1);

// Health check endpoint (diperlukan untuk Caddy health check)
app.get('/health', (req, res) => {
    res.json({
        status: 'ok',
        uptime: process.uptime(),
        timestamp: new Date().toISOString()
    });
});

// Middleware: log request dengan IP asli
app.use((req, res, next) => {
    // Dengan trust proxy, req.ip adalah IP asli client (dari X-Real-IP)
    console.log(`${req.ip} ${req.method} ${req.path}`);
    next();
});

app.listen(3000, '127.0.0.1', () => {
    // Bind ke localhost saja — Caddy yang expose ke internet
    console.log('App listening on localhost:3000');
});

Monitoring dan Alerting #

# Monitor PM2 status
pm2 status
pm2 monit  # Real-time monitoring

# Cek error log
pm2 logs myapp --err --lines 50

# Setup Caddy health check monitoring
watch -n 30 'curl -sf https://example.com/health | jq .'

# Script alert sederhana
#!/bin/bash
HEALTH=$(curl -sf https://example.com/health -o /dev/null -w "%{http_code}" 2>/dev/null)
if [ "$HEALTH" != "200" ]; then
    # Kirim notifikasi
    curl -X POST "https://hooks.slack.com/services/..." \
        -H "Content-Type: application/json" \
        -d '{"text": "🚨 Production down! Health check failed."}'
fi

Ringkasan #

  • Cukup satu baris reverse_proxy localhost:3000 untuk konfigurasi dasar — Caddy otomatis handle TLS, HTTP/2, dan redirect.
  • Selalu tambahkan header_up X-Real-IP {remote_host} dan set app.set('trust proxy', 1) di Express agar aplikasi mendapatkan IP asli client.
  • Gunakan PM2 cluster mode untuk memanfaatkan semua CPU core dan melakukan zero-downtime reload.
  • Sajikan aset statis (JS, CSS, gambar) langsung dari Caddy tanpa melewati Node.js — lebih efisien dan mengurangi beban aplikasi.
  • Tambahkan /health endpoint di aplikasi dan konfigurasikan Caddy health_uri untuk pemantauan otomatis dan traffic failover.
  • Bind Node.js ke 127.0.0.1 bukan 0.0.0.0 — hanya Caddy yang boleh mengakses port 3000, bukan internet langsung.

← Sebelumnya: Buat Plugin   Berikutnya: PHP-FPM →


Konfigurasi CORS untuk Node.js API #

api.example.com {
    @options method OPTIONS
    handle @options {
        header Access-Control-Allow-Origin      "https://app.example.com"
        header Access-Control-Allow-Methods     "GET, POST, PUT, DELETE, OPTIONS"
        header Access-Control-Allow-Headers     "Content-Type, Authorization"
        header Access-Control-Allow-Credentials "true"
        header Access-Control-Max-Age           "86400"
        respond "" 204
    }
    
    header Access-Control-Allow-Origin      "https://app.example.com"
    header Access-Control-Allow-Credentials "true"
    
    encode gzip zstd
    
    reverse_proxy localhost:3000 {
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-Proto {scheme}
        health_uri /health
        health_interval 10s
    }
}

Dengan mengkonfigurasi CORS di Caddy (bukan di aplikasi Node.js), kamu cukup mengkonfigurasi di satu tempat meski punya banyak service Node.js di belakang gateway yang sama.


Streaming Response dari Node.js #

// app.js — contoh streaming response
app.get('/stream', (req, res) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    
    // Kirim event setiap detik
    const interval = setInterval(() => {
        res.write(`data: ${JSON.stringify({ time: new Date() })}\n\n`);
    }, 1000);
    
    req.on('close', () => {
        clearInterval(interval);
    });
});
example.com {
    reverse_proxy localhost:3000 {
        # Untuk Server-Sent Events: nonaktifkan buffering
        flush_interval -1    # Flush segera, tidak buffer
    }
}
About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact