syntaxis/docs/skill_api-server-development.md
Jesús Pérez 9cef9b8d57 refactor: consolidate configuration directories
Merge _configs/ into config/ for single configuration directory.
Update all path references.

Changes:
- Move _configs/* to config/
- Update .gitignore for new patterns
- No code references to _configs/ found

Impact: -1 root directory (layout_conventions.md compliance)
2025-12-26 18:36:23 +00:00

6.6 KiB

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:

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<TlsConfig>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TlsConfig {
    pub enabled: bool,
    pub cert_path: Option<String>,
    pub key_path: Option<String>,
}

impl ServerConfig {
    pub fn load<P: AsRef<std::path::Path>>(config_path: P) -> Result<Self, ConfigError> {
        // Extract [server] section explicitly
        // Fail if any required field missing
        // Return clear error messages
    }

    pub fn socket_addr(&self) -> Result<SocketAddr, ConfigError> { ... }
    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:

clap = { version = "4.4", features = ["derive"] }

In src/main.rs:

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<dyn std::error::Error>> {
    let args = Args::parse();
    let cfg = ServerConfig::load(&args.config)?;
    cfg.validate_tls()?;
    // ... rest of initialization
}

3. Tracing from Config

// 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

if cfg.cors_enabled {
    router = router.layer(CorsLayer::permissive());
    tracing::debug!("CORS enabled");
}

5. Config-Driven Static Files

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 <tool>-api-config.template.toml:

# <Tool> API Server Configuration Template
#
# Usage: <tool>-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-<tool>-api.sh:

#!/bin/bash
set -e

CONFIG_DIR="$HOME/.config/<tool>-api"
BINARY="${PROJECT_ROOT}/target/release/<tool>-server"

mkdir -p "$CONFIG_DIR"
cp <tool>-api-config.template.toml "$CONFIG_DIR/<tool>-api-config.toml"

cat > "$CONFIG_DIR/run-<tool>-server.sh" << 'EOF'
#!/bin/bash
exec BINARY_PATH --config CONFIG_FILE_PATH "$@"
EOF

sed -i '' "s|BINARY_PATH|$BINARY|g" "$CONFIG_DIR/run-<tool>-server.sh"
sed -i '' "s|CONFIG_FILE_PATH|$CONFIG_DIR/<tool>-api-config.toml|g" "$CONFIG_DIR/run-<tool>-server.sh"
chmod +x "$CONFIG_DIR/run-<tool>-server.sh"

echo "✅ Installation complete!"
echo "Run: $CONFIG_DIR/run-<tool>-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

  • <tool>-api-config.template.toml in project root
  • scripts/install-<tool>-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