Production Features Guide
This guide covers production-ready features: rate limiting, CSRF protection, file uploads, compression, and more.
Table of Contents
- Rate Limiting
- CSRF Protection
- File Uploads
- Response Compression
- Health Checks
- Logging
- Security Best Practices
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 generatefor 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!