Skip to content

Requests & Responses

Handlers receive a context object tailored to each binding but backed by the same Rust data model.

Read request data

from spikard import Spikard
from msgspec import Struct

app = Spikard()


class Order(Struct):
    id: int
    item: str
    quantity: int
    verbose: bool | None = None


@app.post("/orders/{order_id:int}")
async def update_order(order_id: int, order: Order, verbose: bool | None = False) -> Order:
    return Order(id=order_id, item=order.item, quantity=order.quantity, verbose=verbose or False)
import { Spikard, type Request } from "spikard";
import { z } from "zod";

const OrderSchema = z.object({
  id: z.number(),
  item: z.string(),
  quantity: z.number().int().positive(),
  verbose: z.boolean().optional(),
});
type Order = z.infer<typeof OrderSchema>;

const app = new Spikard();

const updateOrder = async (req: Request): Promise<Order> => {
  const order = OrderSchema.parse(req.json());
  return { ...order, id: Number(req.params["order_id"] ?? order.id) };
};

app.addRoute(
  {
    method: "POST",
    path: "/orders/:order_id",
    handler_name: "updateOrder",
    request_schema: OrderSchema,
    response_schema: OrderSchema,
    is_async: true,
  },
  updateOrder,
);
require "spikard"

app = Spikard::App.new

app.post("/orders/:order_id") do |params, query, body|
  {
    **body,
    id: params[:order_id].to_i,
    verbose: query["verbose"] == "true",
  }
end
<?php

declare(strict_types=1);

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

final class OrderController
{
    #[Post('/orders/{order_id}')]
    public function create(Request $request): Response
    {
        // Path parameters
        $orderId = (int) $request->pathParams['order_id'];

        // Request body (parsed JSON)
        $order = $request->body;
        $item = $order['item'] ?? '';
        $quantity = (int) ($order['quantity'] ?? 0);

        // Query parameters
        $verbose = ($request->queryParams['verbose'][0] ?? 'false') === 'true';

        // Headers
        $contentType = $request->headers['content-type'] ?? 'unknown';

        return Response::json([
            'id' => $orderId,
            'item' => $item,
            'quantity' => $quantity,
            'verbose' => $verbose,
            'content_type' => $contentType
        ]);
    }
}

$app = (new App(new ServerConfig(port: 8000)))
    ->registerController(new OrderController());
app.route(post("/orders/:order_id"), |ctx: Context| async move {
    let mut order: serde_json::Value = ctx.json()?;
    let id = ctx.path_param("order_id").unwrap_or("0");
    let verbose: serde_json::Value = ctx.query().unwrap_or_default();
    if let Some(map) = order.as_object_mut() {
        map.insert("id".into(), serde_json::json!(id.parse::<i64>().unwrap_or_default()));
        map.insert("verbose".into(), verbose.get("verbose").cloned().unwrap_or(serde_json::json!(false)));
    }
    Ok(Json(order))
})?;

Return responses

@app.get("/health")
async def health() -> dict:
    return {"status": "ok"}
import { Spikard } from "spikard";

const app = new Spikard();

app.addRoute(
  { method: "GET", path: "/health", handler_name: "health", is_async: true },
  async () => ({ status: "ok" }),
);
require "spikard"

app = Spikard::App.new

app.get("/health") { |_params, _query, _body| { status: "ok" } }
<?php

declare(strict_types=1);

use Spikard\Http\Response;

// JSON response
return Response::json(['status' => 'ok']);

// JSON with status code
return Response::json(['error' => 'not found'], 404);

// Plain text response
return Response::text('Hello, World!');

// Response with custom headers
return new Response(
    body: ['data' => 'value'],
    statusCode: 200,
    headers: ['X-Custom-Header' => 'value']
);
app.route(get("/health"), |_ctx: Context| async {
    Ok(Json(serde_json::json!({"status": "ok"})))
})?;

Tips

  • Use DTOs/schemas so validation runs before your handler executes.
  • Prefer returning plain values/structs; the runtime will serialize and set content types.
  • For streaming/WebSocket/SSE, see the streaming section in the concepts docs.