Production Features Guide

This guide covers production-ready features: rate limiting, CSRF protection, file uploads, compression, and more.

Table of Contents


Rate Limiting

Note: Production features use standard middleware imports:

import leafscale.varel.app as varel_app
import leafscale.varel.http
import leafscale.varel.middleware

Why Rate Limiting?

Rate limiting prevents abuse:

  • Protects against DDoS attacks
  • Prevents brute-force login attempts
  • Limits API usage per user
  • Ensures fair resource allocation

Basic Rate Limiting

import leafscale.varel.middleware

fn main() {
    mut web_app := varel_app.new('My App')

    // Global rate limit: 100 requests per minute per IP
    web_app.use(middleware.rate_limit_default())

    web_app.get('/', index)!
    web_app.listen(':8080')
}

Strict Rate Limiting

// API routes with stricter limits
api := web_app.group('/api')
api.use(middleware.rate_limit_strict())  // 10 requests per minute
api.get('/data', api_data)!

Custom Configuration

import time

web_app.use(middleware.rate_limit(middleware.RateLimitConfig{
    requests_per_window: 50
    window_duration: time.minute
    message: 'Too many requests, please slow down'
}))

Per-User Rate Limiting

// Rate limit by authenticated user instead of IP
web_app.use(middleware.rate_limit_by_user(middleware.RateLimitConfig{
    requests_per_window: 1000
    window_duration: time.hour
}))

Per-Path Rate Limiting

// Different limits per endpoint
expensive := web_app.group('/api/expensive')
expensive.use(middleware.rate_limit_strict())  // 10/min
expensive.get('/query', expensive_query)!

cheap := web_app.group('/api/cheap')
cheap.use(middleware.rate_limit_default())  // 100/min
cheap.get('/ping', ping)!

CSRF Protection

What is CSRF?

Cross-Site Request Forgery tricks users into submitting malicious requests. CSRF protection validates that requests originate from your application.

Enable CSRF Protection

import leafscale.varel.middleware

fn main() {
    mut web_app := varel_app.new('My App')

    // CSRF protection for forms
    // Use varel secret generate to create a secure key
    csrf_secret := os.getenv('CSRF_SECRET') or { config.session.secret_key }
    web_app.use(middleware.csrf(middleware.CSRFConfig{
        secret: csrf_secret
        cookie_secure: true  // Require HTTPS
    }))

    web_app.get('/form', show_form)!
    web_app.post('/submit', submit_form)!

    web_app.listen(':8080')
}

Forms with CSRF Tokens

// Controller
fn show_form(mut ctx http.Context) http.Response {
    csrf_token := ctx.get('csrf_token')

    return ctx.render('form', {
        'csrf_token': csrf_token
    })
}
<!-- Template -->
<form action="/submit" method="POST">
    <!-- CSRF token hidden field -->
    <input type="hidden" name="_csrf_token" value="${csrf_token}">

    <input type="text" name="data">
    <button type="submit">Submit</button>
</form>

AJAX Requests

<meta name="csrf-token" content="${csrf_token}">

<script>
// Get CSRF token from meta tag
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

// Include in AJAX requests
fetch('/api/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-Token': csrfToken
    },
    body: JSON.stringify({data: 'value'})
});
</script>

File Uploads

Configure File Uploads

import leafscale.varel.http

fn handle_upload(mut ctx http.Context) http.Response {
    // Configure upload
    upload_config := http.UploadConfig{
        max_file_size: 10 * 1024 * 1024  // 10MB
        allowed_types: [
            'image/jpeg',
            'image/png',
            'application/pdf',
        ]
        temp_dir: './uploads/temp'
        keep_extensions: true
    }

    // Parse multipart form
    form := ctx.parse_multipart_form(upload_config) or {
        return ctx.bad_request('Failed to parse upload: ${err}')
    }

    // Get uploaded file
    files := form.files['file'] or {
        http.cleanup_multipart_form(&form)
        return ctx.bad_request('No file uploaded')
    }

    uploaded_file := files[0]

    // Save file
    dest_path := './uploads/${uploaded_file.filename}'
    http.save_uploaded_file(&uploaded_file, dest_path) or {
        http.cleanup_multipart_form(&form)
        return ctx.internal_error('Failed to save file')
    }

    // Clean up temp files
    http.cleanup_multipart_form(&form)

    return ctx.ok('File uploaded successfully')
}

Upload Form

<form action="/upload" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="_csrf_token" value="${csrf_token}">

    <label>Choose file:</label>
    <input type="file" name="file" required>

    <label>Description:</label>
    <input type="text" name="description">

    <button type="submit">Upload</button>
</form>

File Validation

// Validate file
uploaded_file := files[0]

// Check size
if uploaded_file.size > 5 * 1024 * 1024 {
    return ctx.bad_request('File too large (max 5MB)')
}

// Check type
if uploaded_file.content_type !in ['image/jpeg', 'image/png'] {
    return ctx.bad_request('Invalid file type')
}

// Check extension
ext := os.file_ext(uploaded_file.filename)
if ext !in ['.jpg', '.jpeg', '.png'] {
    return ctx.bad_request('Invalid file extension')
}

Response Compression

Enable Compression

import leafscale.varel.middleware

fn main() {
    mut web_app := varel_app.new('My App')

    // Default compression (level 6)
    web_app.use(middleware.compression_default())

    web_app.get('/', index)!
    web_app.listen(':8080')
}

Compression Levels

// Fast compression (level 1) - Less CPU, larger files
web_app.use(middleware.compression_fast())

// Best compression (level 9) - More CPU, smaller files
web_app.use(middleware.compression_best())

// Custom level
web_app.use(middleware.compression(middleware.CompressionConfig{
    level: 7  // 1-9
    min_size: 1024  // Only compress >= 1KB
    exclude_paths: ['/api/stream']  // Don't compress these paths
}))

What Gets Compressed?

Automatically compresses:

  • HTML, CSS, JavaScript
  • JSON, XML
  • Plain text
  • Any response >= 1KB

Does NOT compress:

  • Images (already compressed)
  • Videos
  • PDFs
  • Binary files

Health Checks

Add Health Check Endpoint

import leafscale.varel.middleware

fn main() {
    mut web_app := varel_app.new('My App')

    // Add /health endpoint
    web_app.use(middleware.health(middleware.HealthConfig{
        path: '/health'
        checker: fn () bool {
            // Check if database is responsive
            database.ping() or { return false }

            // Check if cache is responsive
            cache.ping() or { return false }

            // All checks passed
            return true
        }
    }))

    web_app.listen(':8080')
}

Health Check Response

Healthy:

GET /health
200 OK
{"status": "ok"}

Unhealthy:

GET /health
503 Service Unavailable
{"status": "error"}

Use with Load Balancers

Load balancers (Caddy, nginx, etc.) use health checks to know which servers are healthy:

Caddyfile:
reverse_proxy localhost:8080 {
    health_uri /health
    health_interval 10s
}

Logging

Structured Logging

import leafscale.varel.middleware

fn main() {
    mut web_app := varel_app.new('My App')

    // Development - Colored, human-readable
    web_app.use(middleware.logger_default())

    // Production - JSON format
    web_app.use(middleware.logger(middleware.LoggerConfig{
        format: .json
        output: .stdout
        colors: false
    }))

    web_app.listen(':8080')
}

Log Formats

Dev format:

GET /products 200 45ms
POST /products 201 123ms

Common Log Format (Apache):

127.0.0.1 - - [13/Oct/2025:17:30:00 +0000] "GET /products HTTP/1.1" 200 1234

JSON format:

{"method":"GET","path":"/products","status":200,"duration_ms":45,"timestamp":"2025-10-13T17:30:00Z"}

Log Levels

// In your handlers
eprintln('ERROR: Failed to connect to database: ${err}')  // Error
println('INFO: User ${user_id} logged in')  // Info

Security Best Practices

Secret Key Management

Varel automatically generates secure session secrets during project creation. For production deployments, follow these practices:

1. Generate Production Secrets

# Generate and update secret in config
varel secret generate --replace

# Or generate for environment variable
export SESSION_SECRET=$(varel secret generate)
export CSRF_SECRET=$(varel secret generate)

2. Environment-Specific Secrets

Each environment should have unique secrets:

# Development (auto-generated during varel new)
# Uses config/config.toml

# Staging
cd staging
varel secret generate --replace

# Production
cd production
varel secret generate --replace

3. Secret Rotation

Regularly rotate secrets for enhanced security:

# Every 90 days or after security incidents
varel secret generate --replace

# Restart application to use new secret
systemctl restart myapp

4. Never Commit Secrets

Add to .gitignore:

config/config.toml  # Contains secrets
.env                # Environment variables

Use environment variables for production:

# .env (not committed)
SESSION_SECRET=92d7e1d6cd4158fd92c58f717871f32ff88b11a2e41644de20f9e5abadd62302
CSRF_SECRET=05fc043d8f61efbc0aaa9f153f23623a9bc7bd83d29af82f182edc5c147cb401
DB_PASSWORD=your-secure-password

Security Checklist:

  • ✅ Unique secrets per environment (dev/staging/prod)
  • ✅ Rotate secrets every 90 days minimum
  • ✅ Use varel secret generate for cryptographically secure keys
  • ✅ Never commit secrets to version control
  • ✅ Use environment variables in production
  • ✅ Minimum 32 bytes (64 hex characters) for all secrets
  • ✅ HTTPS only in production (secure: true)
  • ✅ HttpOnly cookies (http_only: true)
  • ✅ CSRF protection enabled
  • ✅ Rate limiting configured

Summary

You've learned:

✅ Rate limiting to prevent abuse ✅ CSRF protection for forms ✅ File uploads with validation ✅ Response compression for performance ✅ Health checks for monitoring ✅ Structured logging for production ✅ Secret key management and rotation

Continue to the Deployment Guide to deploy your application!