Skip to content

Quick Start

Build the same minimal service in each binding. Choose a tab, copy the snippet, and run.

Define routes

from spikard import Spikard
from spikard.config import ServerConfig
from msgspec import Struct

class User(Struct):
    id: int
    name: str

app = Spikard()

@app.get("/users/{id:int}")
async def get_user(id: int) -> User:
    return User(id=id, name="Alice")

@app.post("/users")
async def create_user(user: User) -> User:
    return user

if __name__ == "__main__":
    app.run(config=ServerConfig(port=8000))
import { Spikard, type Request } from "spikard";
import { z } from "zod";

const UserSchema = z.object({ id: z.number(), name: z.string() });
type User = z.infer<typeof UserSchema>;

const app = new Spikard();

app.addRoute(
  { method: "GET", path: "/users/:id", handler_name: "getUser", is_async: true },
  async (req: Request): Promise<User> => {
    const id = Number(req.params["id"] ?? 0);
    return { id, name: "Alice" };
  },
);

app.addRoute(
  {
    method: "POST",
    path: "/users",
    handler_name: "createUser",
    request_schema: UserSchema,
    response_schema: UserSchema,
    is_async: true,
  },
  async (req: Request): Promise<User> => UserSchema.parse(req.json()),
);

if (require.main === module) {
  app.run({ port: 8000 });
}
require "spikard"

app = Spikard::App.new

app.get("/users/:id") do |params, _query, _body|
  { id: params[:id].to_i, name: "Alice" }
end

app.post("/users") do |_params, _query, body|
  user = body
  { id: user["id"], name: user["name"] }
end

app.run(config: { port: 8000 })
<?php

declare(strict_types=1);

use Spikard\App;
use Spikard\Attributes\Get;
use Spikard\Attributes\Post;
use Spikard\Config\ServerConfig;
use Spikard\Http\Request;
use Spikard\Http\Response;

final class UsersController
{
    #[Get('/users')]
    public function list(): Response
    {
        return Response::json([
            'users' => [
                ['id' => 1, 'name' => 'Alice'],
                ['id' => 2, 'name' => 'Bob'],
            ]
        ]);
    }

    #[Get('/users/{id}')]
    public function show(Request $request): Response
    {
        $userId = (int) $request->pathParams['id'];
        return Response::json(['id' => $userId, 'name' => 'Alice']);
    }

    #[Post('/users')]
    public function create(Request $request): Response
    {
        $user = $request->body;
        return Response::json($user, 201);
    }
}

$app = (new App(new ServerConfig(port: 8000)))
    ->registerController(new UsersController());

$app->run();
use axum::response::Json;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use spikard::{get, post, App, RequestContext};

#[derive(Serialize, Deserialize, JsonSchema)]
struct User {
    id: i64,
    name: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut app = App::new();

    app.route(get("/users/:id"), |ctx: RequestContext| async move {
        let id = ctx.path_param("id").unwrap_or("0").parse::<i64>().unwrap_or_default();
        Ok(Json(User { id, name: "Alice".into() }).into())
    })?;

    app.route(
        post("/users").request_body::<User>().response_body::<User>(),
        |ctx: RequestContext| async move {
            let user: User = ctx.json()?;
            Ok(Json(user).into())
        },
    )?;

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

Run it

python app.py

Then hit http://localhost:8000/users/1

pnpm ts-node app.ts

Then hit http://localhost:8000/users/1

ruby app.rb

Then hit http://localhost:8000/users/1

<?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(8000)
    ->withWorkers(4)
    ->build();

$app = (new App($config))
    ->registerController(new HealthController());

$app->run();
cargo run

inside your crate/binary, then hit http://localhost:8000/users/1

Next steps

  • Add middleware (logging, auth, tracing) with the same signature in every binding.
  • Wire JSON Schema validation so request/response contracts stay enforced.
  • Deploy using the Rust binary, the CLI, or container images (see Deployment).