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,
);
<?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¶
<?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']
);
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.