Skip to content

Dependency Injection

Register shared services once and inject them into handlers by name. Value dependencies are singletons; factory dependencies can be request-scoped (cacheable) or singletons.

Register dependencies

from spikard import Spikard
from spikard.di import Provide

app = Spikard()

# Value dependency (singleton)
app.provide("config", {"db_url": "postgresql://localhost/app"})

# Factory dependency (depends on config, cached globally)
async def make_pool(config: dict[str, str]):
    return {"url": config["db_url"], "client": "pool"}

app.provide("db_pool", Provide(make_pool, depends_on=["config"], singleton=True))

@app.get("/stats")
async def stats(config: dict[str, str], db_pool: dict[str, str]):
    return {"db": db_pool["url"], "env": config["db_url"]}
import { Spikard, type Request } from "spikard";

const app = new Spikard();

// Value dependency (singleton)
app.provide("config", { dbUrl: "postgresql://localhost/app" });

// Factory dependency (depends on config, singleton)
app.provide(
  "dbPool",
  async ({ config }) => {
    // connect using config.dbUrl
    return { url: config.dbUrl, driver: "pool" };
  },
  { dependsOn: ["config"], singleton: true },
);

app.addRoute(
  { method: "GET", path: "/stats", handler_name: "stats", is_async: true },
  async (req: Request) => {
    const deps = req.dependencies ?? {};
    return { db: deps.dbPool?.url, env: deps.config?.dbUrl };
  },
);
require "spikard"

app = Spikard::App.new

# Value dependency (singleton)
app.provide("config", { "db_url" => "postgresql://localhost/app" })

# Factory dependency (depends on config, singleton)
app.provide("db_pool", depends_on: ["config"], singleton: true) do |config:|
  { url: config["db_url"], client: "pool" }
end

app.get("/stats") do |_params, _query, _body, config:, db_pool:|
  { db: db_pool[:url], env: config["db_url"] }
end
<?php

declare(strict_types=1);

use Spikard\App;
use Spikard\Attributes\Get;
use Spikard\Config\ServerConfig;
use Spikard\DI\DependencyContainer;
use Spikard\DI\Provide;
use Spikard\Http\Response;

// Create dependency container with values and factories
$container = DependencyContainer::builder()
    ->withValue('config', ['db_url' => 'postgresql://localhost/app'])
    ->withFactory(
        'db_pool',
        new Provide(
            factory: fn (array $config) => [
                'url' => $config['db_url'],
                'client' => 'pool'
            ],
            dependsOn: ['config'],
            singleton: true
        )
    )
    ->build();

final class StatsController
{
    #[Get('/stats')]
    public function stats(): Response
    {
        return Response::json(['status' => 'ok', 'db' => 'connected']);
    }
}

$app = (new App(new ServerConfig(port: 8000)))
    ->withDependencies($container)
    ->registerController(new StatsController());
use axum::response::Json;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use spikard::{get, App, RequestContext};
use spikard_http::ServerConfig;
use std::sync::Arc;

#[derive(Serialize, Deserialize, JsonSchema)]
struct Health {
    db: String,
    env: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Register dependencies on the server config
    let config = ServerConfig::builder()
        .provide_value("config", "postgresql://localhost/app".to_string())
        .provide_factory("db_pool", |resolved| async move {
            let url: Arc<String> = resolved.get("config").ok_or("missing config")?;
            Ok(format!("pool({})", url))
        })
        .build();

    let mut app = App::new().config(config);

    app.route(get("/stats"), |ctx: RequestContext| async move {
        let deps = ctx.dependencies();
        let db = deps.and_then(|d| d.get::<String>("db_pool")).cloned().unwrap_or_default();
        let env = deps
            .and_then(|d| d.get::<String>("config"))
            .cloned()
            .unwrap_or_else(|| "unknown".to_string());
        Ok(Json(Health { db, env }).into())
    })?;

    app.run().await?;
    Ok(())
}

Notes

  • Value dependencies are cached globally. Use factories for per-request values and set cacheable/use_cache when you need a fresh value each time.
  • Factories can depend on other dependencies; unresolved or circular graphs fail fast with clear errors.
  • Cleanup generators (Python) and singleton toggles (TypeScript/Ruby) mirror the core DI engine semantics.