Configuration¶
Spikard favors code-based configuration with environment overrides. The same knobs exist regardless of binding.
Basic ServerConfig¶
Create and configure your server with explicit settings:
from spikard import Spikard
from spikard.config import ServerConfig
config = ServerConfig(
host="0.0.0.0",
port=8080,
workers=4,
request_timeout=60,
max_body_size=5 * 1024 * 1024, # 5MB
)
app = Spikard(config=config)
@app.get("/health")
async def health():
return {"status": "ok"}
if __name__ == "__main__":
app.run()
import { Spikard, runServer } from "spikard";
import type { ServerConfig, Request } from "spikard";
const config: ServerConfig = {
host: "0.0.0.0",
port: 8080,
workers: 4,
requestTimeout: 60,
maxBodySize: 5 * 1024 * 1024, // 5MB
};
const app = new Spikard();
app.addRoute(
{ method: "GET", path: "/health", handler_name: "health", is_async: true },
async (req: Request) => ({ status: "ok" }),
);
if (require.main === module) {
runServer(app, config);
}
<?php
declare(strict_types=1);
use Spikard\App;
use Spikard\Attributes\Get;
use Spikard\Config\ServerConfig;
use Spikard\Http\Response;
final class HealthController
{
#[Get('/health')]
public function health(): Response
{
return Response::json(['status' => 'ok']);
}
}
$config = ServerConfig::builder()
->withHost('0.0.0.0')
->withPort(8080)
->withWorkers(4)
->withRequestTimeout(60)
->withMaxBodySize(5 * 1024 * 1024) // 5MB
->build();
$app = (new App($config))
->registerController(new HealthController());
$app->run();
use axum::response::Json;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use spikard::{get, App, ServerConfig};
#[derive(Serialize, Deserialize, JsonSchema)]
struct HealthResponse {
status: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ServerConfig::builder()
.host("0.0.0.0")
.port(8080)
.workers(4)
.request_timeout_secs(60)
.max_body_size(5 * 1024 * 1024) // 5MB
.build();
let mut app = App::with_config(config);
app.route(get("/health"), |_ctx| async move {
Ok(Json(HealthResponse { status: "ok".into() }).into())
})?;
app.run().await?;
Ok(())
}
Environment Variable Overrides¶
Override config at runtime without changing code:
import os
from spikard import Spikard
from spikard.config import ServerConfig
config = ServerConfig(
host=os.getenv("SPIKARD_HOST", "127.0.0.1"),
port=int(os.getenv("SPIKARD_PORT", "8000")),
workers=int(os.getenv("SPIKARD_WORKERS", "1")),
request_timeout=int(os.getenv("SPIKARD_TIMEOUT", "30")),
)
app = Spikard(config=config)
# Keep secrets in env
api_key = os.getenv("API_KEY")
db_url = os.getenv("DATABASE_URL")
import { Spikard, runServer } from "spikard";
import type { ServerConfig } from "spikard";
const config: ServerConfig = {
host: process.env.SPIKARD_HOST || "127.0.0.1",
port: parseInt(process.env.SPIKARD_PORT || "8000"),
workers: parseInt(process.env.SPIKARD_WORKERS || "1"),
requestTimeout: parseInt(process.env.SPIKARD_TIMEOUT || "30"),
};
const app = new Spikard();
// Keep secrets in env
const apiKey = process.env.API_KEY;
const dbUrl = process.env.DATABASE_URL;
runServer(app, config);
require "spikard"
config = Spikard::ServerConfig.new(
host: ENV.fetch("SPIKARD_HOST", "127.0.0.1"),
port: ENV.fetch("SPIKARD_PORT", "8000").to_i,
workers: ENV.fetch("SPIKARD_WORKERS", "1").to_i,
request_timeout: ENV.fetch("SPIKARD_TIMEOUT", "30").to_i
)
app = Spikard::App.new(config: config)
# Keep secrets in env
api_key = ENV["API_KEY"]
db_url = ENV["DATABASE_URL"]
<?php
declare(strict_types=1);
use Spikard\App;
use Spikard\Config\ServerConfig;
$config = ServerConfig::builder()
->withHost(getenv('SPIKARD_HOST') ?: '127.0.0.1')
->withPort((int) (getenv('SPIKARD_PORT') ?: 8000))
->withWorkers((int) (getenv('SPIKARD_WORKERS') ?: 1))
->withRequestTimeout((int) (getenv('SPIKARD_TIMEOUT') ?: 30))
->build();
$app = new App($config);
// Keep secrets in env
$apiKey = getenv('API_KEY');
$dbUrl = getenv('DATABASE_URL');
use spikard::{App, ServerConfig};
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load from .env file if present
dotenv::dotenv().ok();
let config = ServerConfig::builder()
.host(env::var("SPIKARD_HOST").unwrap_or_else(|_| "127.0.0.1".into()))
.port(
env::var("SPIKARD_PORT")
.ok()
.and_then(|p| p.parse().ok())
.unwrap_or(8000),
)
.workers(
env::var("SPIKARD_WORKERS")
.ok()
.and_then(|w| w.parse().ok())
.unwrap_or(1),
)
.request_timeout_secs(
env::var("SPIKARD_TIMEOUT")
.ok()
.and_then(|t| t.parse().ok())
.unwrap_or(30),
)
.build();
let app = App::with_config(config);
// Keep secrets in env
let api_key = env::var("API_KEY").ok();
let db_url = env::var("DATABASE_URL").ok();
app.run().await?;
Ok(())
}
Run with: SPIKARD_PORT=8080 SPIKARD_WORKERS=4 python app.py
Production Configuration¶
Full production setup with compression, rate limiting, and monitoring:
import os
from spikard import Spikard
from spikard.config import (
CompressionConfig,
OpenApiConfig,
RateLimitConfig,
ServerConfig,
)
config = ServerConfig(
host="0.0.0.0",
port=8080,
workers=4,
request_timeout=60,
max_body_size=10 * 1024 * 1024,
# High-quality compression
compression=CompressionConfig(
gzip=True,
brotli=True,
min_size=1024,
quality=6,
),
# Protect against abuse
rate_limit=RateLimitConfig(
per_second=100,
burst=200,
ip_based=True,
),
# Auto-generated docs
openapi=OpenApiConfig(
enabled=True,
title="Production API",
version="1.0.0",
),
# Graceful shutdown
graceful_shutdown=True,
shutdown_timeout=30,
)
app = Spikard(config=config)
import { Spikard, runServer } from "spikard";
import type { ServerConfig } from "spikard";
const config: ServerConfig = {
host: "0.0.0.0",
port: 8080,
workers: 4,
requestTimeout: 60,
maxBodySize: 10 * 1024 * 1024,
// High-quality compression
compression: {
gzip: true,
brotli: true,
minSize: 1024,
quality: 6,
},
// Protect against abuse
rateLimit: {
perSecond: 100,
burst: 200,
ipBased: true,
},
// Auto-generated docs
openapi: {
enabled: true,
title: "Production API",
version: "1.0.0",
},
// Graceful shutdown
gracefulShutdown: true,
shutdownTimeout: 30,
};
const app = new Spikard();
runServer(app, config);
require "spikard"
config = Spikard::ServerConfig.new(
host: "0.0.0.0",
port: 8080,
workers: 4,
request_timeout: 60,
max_body_size: 10 * 1024 * 1024,
# High-quality compression
compression: Spikard::CompressionConfig.new(
gzip: true,
brotli: true,
min_size: 1024,
quality: 6
),
# Protect against abuse
rate_limit: Spikard::RateLimitConfig.new(
per_second: 100,
burst: 200,
ip_based: true
),
# Auto-generated docs
openapi: Spikard::OpenApiConfig.new(
enabled: true,
title: "Production API",
version: "1.0.0"
),
# Graceful shutdown
graceful_shutdown: true,
shutdown_timeout: 30
)
app = Spikard::App.new(config: config)
<?php
declare(strict_types=1);
use Spikard\App;
use Spikard\Config\CompressionConfig;
use Spikard\Config\OpenApiConfig;
use Spikard\Config\RateLimitConfig;
use Spikard\Config\ServerConfig;
// Production error handling
error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
$config = ServerConfig::builder()
->withHost('0.0.0.0')
->withPort(8080)
->withWorkers(4)
->withRequestTimeout(60)
->withMaxBodySize(10 * 1024 * 1024)
// High-quality compression
->withCompression(
CompressionConfig::builder()
->withGzip(true)
->withBrotli(true)
->withMinSize(1024)
->withQuality(6)
->build()
)
// Protect against abuse
->withRateLimit(
RateLimitConfig::builder()
->withPerSecond(100)
->withBurst(200)
->withIpBased(true)
->build()
)
// Auto-generated docs
->withOpenApi(
OpenApiConfig::builder()
->withEnabled(true)
->withTitle('Production API')
->withVersion('1.0.0')
->build()
)
// Graceful shutdown
->withGracefulShutdown(true)
->withShutdownTimeout(30)
->build();
$app = new App($config);
use spikard::{
App, CompressionConfig, OpenApiConfig, RateLimitConfig, ServerConfig,
};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Production logging setup
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer().json())
.with(tracing_subscriber::EnvFilter::from_default_env())
.init();
// Panic handling for production
std::panic::set_hook(Box::new(|info| {
tracing::error!("Panic occurred: {:?}", info);
}));
let config = ServerConfig::builder()
.host("0.0.0.0")
.port(8080)
.workers(4)
.request_timeout_secs(60)
.max_body_size(10 * 1024 * 1024)
// High-quality compression
.compression(
CompressionConfig::builder()
.gzip(true)
.brotli(true)
.min_size(1024)
.quality(6)
.build(),
)
// Protect against abuse
.rate_limit(
RateLimitConfig::builder()
.per_second(100)
.burst(200)
.ip_based(true)
.build(),
)
// Auto-generated docs
.openapi(
OpenApiConfig::builder()
.enabled(true)
.title("Production API")
.version("1.0.0")
.build(),
)
// Graceful shutdown
.graceful_shutdown(true)
.shutdown_timeout_secs(30)
.build();
let app = App::with_config(config);
app.run().await?;
Ok(())
}
Server Settings¶
host/port– network binding for the HTTP serverworkers– Tokio worker thread count; defaults to 1. Set explicitly (for example to CPU count) in production.keep_alive/request_timeout– tune to match upstream load balancersmax_body_size– prevent memory exhaustion from large uploads
Middleware Defaults¶
- Logging/tracing enabled by default with request IDs
- CORS/compression configurable per app or per route
- Add custom middleware to inject tenant data, auth, or feature flags
Validation Controls¶
- Enable/disable request and response validation globally or per route
- Provide JSON Schemas or rely on derived DTOs
- Customize error formatting for clients
TLS and HTTP/2 (via Reverse Proxy)¶
Spikard listens on plain HTTP. Use a reverse proxy for TLS termination:
upstream spikard_backend {
server 127.0.0.1:8080;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/api.example.com.crt;
ssl_certificate_key /etc/ssl/private/api.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://spikard_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# docker-compose.yml
services:
traefik:
image: traefik:v2.10
command:
- --entrypoints.websecure.address=:443
- --certificatesresolvers.myresolver.acme.tlschallenge=true
ports:
- "443:443"
labels:
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
- "traefik.http.routers.api.tls.certresolver=myresolver"
- "traefik.http.services.api.loadbalancer.server.port=8080"
Verify It Works¶
Use the Taskfile (task docs:serve) to iterate on examples locally.