Quick Start¶
Build the same minimal service in each binding. Choose a tab, copy the snippet, and run.
Define routes¶
from spikard import App, ServerConfig
from msgspec import Struct
class User(Struct):
id: int
name: str
app = App()
@app.get_decorator("/users/{id:int}")
async def get_user(id: int) -> User:
return User(id=id, name="Alice")
@app.post_decorator("/users")
async def create_user(user: User) -> User:
return user
if __name__ == "__main__":
app.config(ServerConfig(port=8000))
app.run()
import { App, ServerConfig } from "@spikard/node";
import { z } from "zod";
const UserSchema = z.object({ id: z.number(), name: z.string() });
type User = z.infer<typeof UserSchema>;
const app = new App();
app.get("/users/:id", async (req) => {
const id = Number(req.params["id"] ?? 0);
return { id, name: "Alice" };
});
app.post("/users", async (req) => {
const body = await req.json();
return UserSchema.parse(body);
});
if (require.main === module) {
app.config(new ServerConfig({ port: 8000 }));
app.run();
}
<?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¶
<?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();
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).