# API Server Development Skill **Purpose**: Provide guidance for creating and reviewing production-grade REST API servers that follow Project Agnostic Practice (PAP) principles. **When to Use**: - Creating a new REST API server - Reviewing existing API servers for compliance - Implementing configuration systems - Adding CORS/TLS/static files support ## The Config-Driven API Server Pattern This skill is based on the **syntaxis-api** implementation, which establishes the standard pattern for all API servers in this project. ### What Makes This Pattern Special ✅ **Portable**: No hardcoded paths - works anywhere ✅ **Explicit**: All configuration required, no defaults ✅ **Safe**: Clear error messages, validation ✅ **Flexible**: Config-driven CORS, logging, static files, TLS ✅ **Graceful**: Missing optional features don't break server ## Quick Start for New API Servers ### 1. Configuration Module Create `src/config.rs`: ```rust use serde::{Deserialize, Serialize}; use thiserror::Error; #[derive(Error, Debug)] pub enum ConfigError { #[error("IO error: {0}")] Io(#[from] std::io::Error), #[error("TOML parsing error: {0}")] TomlError(#[from] toml::de::Error), #[error("Configuration error: {0}")] Custom(String), } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerConfig { pub host: String, pub port: u16, pub database_path: String, pub public_files_path: String, pub cors_enabled: bool, pub log_level: String, #[serde(default)] pub tls: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TlsConfig { pub enabled: bool, pub cert_path: Option, pub key_path: Option, } impl ServerConfig { pub fn load>(config_path: P) -> Result { // Extract [server] section explicitly // Fail if any required field missing // Return clear error messages } pub fn socket_addr(&self) -> Result { ... } pub fn database_path_buf(&self) -> PathBuf { ... } pub fn public_files_path_buf(&self) -> PathBuf { ... } pub fn validate_tls(&self) -> Result<(), ConfigError> { ... } pub fn log_config(&self) { ... } } ``` ### 2. CLI Argument Parsing Add to `Cargo.toml`: ```toml clap = { version = "4.4", features = ["derive"] } ``` In `src/main.rs`: ```rust use clap::Parser; #[derive(Parser)] #[command(name = "tool-server")] struct Args { /// Path to configuration file (TOML format) #[arg(short, long)] config: PathBuf, } #[tokio::main] async fn main() -> Result<(), Box> { let args = Args::parse(); let cfg = ServerConfig::load(&args.config)?; cfg.validate_tls()?; // ... rest of initialization } ``` ### 3. Tracing from Config ```rust // Initialize tracing with configured log level let env_filter = tracing_subscriber::EnvFilter::try_from_default_env() .or_else(|_| tracing_subscriber::EnvFilter::try_new(&cfg.log_level))?; tracing_subscriber::fmt() .with_env_filter(env_filter) .init(); cfg.log_config(); ``` ### 4. Conditional CORS ```rust if cfg.cors_enabled { router = router.layer(CorsLayer::permissive()); tracing::debug!("CORS enabled"); } ``` ### 5. Config-Driven Static Files ```rust let public_files_path = cfg.public_files_path_buf(); let public_files_exist = public_files_path.exists(); if public_files_exist { tracing::info!("Static files directory: {}", public_files_path.display()); } else { tracing::warn!( "Static files directory does not exist: {}. Skipping /public and /docs endpoints.", public_files_path.display() ); } if public_files_exist { router = router .nest_service("/public", ServeDir::new(public_files_path.clone())) .nest_service("/docs", ServeDir::new(public_files_path)); } ``` ### 6. Template Configuration File Create `-api-config.template.toml`: ```toml # API Server Configuration Template # # Usage: -server --config /path/to/config.toml [server] host = "127.0.0.1" port = 3000 database_path = "./data/database.db" public_files_path = "./public" cors_enabled = true log_level = "info" [server.tls] enabled = false # cert_path = "/path/to/cert.pem" # key_path = "/path/to/key.pem" ``` ### 7. Installation Script Create `scripts/install--api.sh`: ```bash #!/bin/bash set -e CONFIG_DIR="$HOME/.config/-api" BINARY="${PROJECT_ROOT}/target/release/-server" mkdir -p "$CONFIG_DIR" cp -api-config.template.toml "$CONFIG_DIR/-api-config.toml" cat > "$CONFIG_DIR/run--server.sh" << 'EOF' #!/bin/bash exec BINARY_PATH --config CONFIG_FILE_PATH "$@" EOF sed -i '' "s|BINARY_PATH|$BINARY|g" "$CONFIG_DIR/run--server.sh" sed -i '' "s|CONFIG_FILE_PATH|$CONFIG_DIR/-api-config.toml|g" "$CONFIG_DIR/run--server.sh" chmod +x "$CONFIG_DIR/run--server.sh" echo "✅ Installation complete!" echo "Run: $CONFIG_DIR/run--server.sh" ``` ## Review Checklist When reviewing an existing API server: ### Configuration System - [ ] Has `--config` CLI argument (clap) - [ ] Loads TOML from file - [ ] Extracts `[server]` table explicitly - [ ] No hardcoded paths - [ ] No env var overrides - [ ] All fields required (no defaults) - [ ] Clear error messages ### Configuration Fields - [ ] `host` (string) - [ ] `port` (u16) - [ ] `database_path` (string) - [ ] `public_files_path` (string) - [ ] `cors_enabled` (bool) - [ ] `log_level` (string) - [ ] `[server.tls]` (optional) ### Features - [ ] Log level applied to tracing - [ ] CORS conditional based on config - [ ] Static files conditional (with existence check) - [ ] TLS validation if enabled - [ ] Configuration logged at startup ### Files - [ ] `-api-config.template.toml` in project root - [ ] `scripts/install--api.sh` - [ ] `README.md` with configuration examples - [ ] Tests for config loading ## Reference Implementation **Location**: `crates/syntaxis-api/` in syntaxis Review this crate for a complete, production-ready example: - Configuration: `src/config.rs` - Server setup: `src/main.rs` - Template: `syntaxis-api-config.template.toml` - Installation: `scripts/install-syntaxis-api.sh` ## Full Documentation See **API_SERVER_GUIDELINE.md** in project root for: - Complete principles - Detailed implementation checklist - Error handling standards - All required patterns ## Key Takeaways 1. **Configuration First**: `--config` is not optional 2. **Be Explicit**: No defaults, no magic 3. **Be Kind**: Clear error messages with file paths 4. **Be Flexible**: Conditional features (CORS, TLS, files) 5. **Be Portable**: No hardcoded paths anywhere 6. **Be Documented**: Template + comments + installation guide