# Getting Started with RustAPI > Build your first API in under 5 minutes. **Canonical story:** [Golden Path](GOLDEN_PATH.md) — handler → OpenAPI → `production_defaults` → optional MCP/deploy. In-repo demo: ```bash cargo run -p rustapi-rs --example golden_path # http://127.0.0.1:8080/docs · /docs/openapi.json · GET /api/v1/ping ``` --- ## Prerequisites - Rust 1.85 or later (MSRV) - Cargo (comes with Rust) ```bash # Check your Rust version rustc --version ``` --- ## Installation Add RustAPI to your `Cargo.toml`: ```toml [dependencies] rustapi-rs = "0.2.2" ``` You can also rename the crate if you prefer shorter macro paths: ```toml [dependencies] api = { package = "rustapi-rs", version = "0.2.2" } ``` Or with specific features: ```toml [dependencies] rustapi-rs = { version = "0.2.2", features = ["extras-jwt", "extras-cors", "protocol-toon", "protocol-ws", "protocol-view"] } ``` ### Available Features | Feature | Description | |---------|-------------| | `core` | Default stable core (`core-openapi`, `core-tracing`) | | `core-openapi` | OpenAPI + docs endpoint support | | `core-tracing` | Tracing middleware and instrumentation | | `protocol-toon` | LLM-optimized TOON format | | `protocol-ws` | WebSocket support | | `protocol-view` | Template engine (Tera) | | `protocol-grpc` | gRPC bridge helpers | | `extras-jwt` | JWT authentication | | `extras-cors` | CORS middleware | | `extras-rate-limit` | IP-based rate limiting | | `extras-config` | Environment/config helpers | | `full` | `core + protocol-all + extras-all` | | `i18n` | Localized validation messages (`rust-i18n`); English fallbacks without it | ### Slim vs Full: Which profile should I use? Most production APIs should start **slim** and opt into extras only when needed: ```toml # Recommended default — fast compiles, ~158 transitive crates [dependencies] rustapi-rs = { version = "0.2.2", default-features = false, features = ["core"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } ``` Add features incrementally (`extras-jwt`, `extras-cors`, `protocol-ws`, …) instead of enabling everything at once. ```toml # Kitchen-sink profile — tutorials and exploration only (~900+ transitive crates) [dependencies] rustapi-rs = { version = "0.2.2", features = ["full"] } ``` | Profile | Transitive crates (approx.) | Compile time | When to use | |---------|----------------------------|--------------|-------------| | `core` only (slim) | ~158 | Fastest | Production APIs, libraries, CI | | `core` + selected `extras-*` / `protocol-*` | ~200–400 | Moderate | Real services with JWT, CORS, SQLx, etc. | | `full` | ~900+ | Slowest | Demos, feature discovery, integration tests | Since **v0.2.0**, `tracing-subscriber` and `rust-i18n` are no longer pulled unconditionally — initialize tracing in `main`, and enable `features = ["i18n"]` when you need Turkish/other validation locales. --- ## Hello World Create a new project: ```bash cargo new hello-rustapi cd hello-rustapi ``` Add the dependency: ```bash cargo add rustapi-rs ``` Edit `src/main.rs`: ```rust use rustapi_rs::prelude::*; #[derive(Serialize, Schema)] struct Message { greeting: String, } #[rustapi_rs::get("/")] async fn hello() -> Json { Json(Message { greeting: "Hello, World!".into(), }) } #[tokio::main] async fn main() -> Result<(), Box> { RustApi::auto().run("127.0.0.1:8080").await } ``` Run it: ```bash cargo run ``` Test it: ```bash # Terminal / PowerShell curl http://localhost:8080/ # Or open in browser # http://localhost:8080/docs ← Swagger UI ``` --- ## Understanding the Code ### 1. The Prelude Import ```rust use rustapi_rs::prelude::*; ``` This imports everything you need: - `RustApi` — Application builder - `Json`, `Path`, `Query`, `State` — Extractors - `Serialize`, `Deserialize` — Serde macros - `Schema` — OpenAPI schema generation - `get`, `post`, `put`, `patch`, `delete` — Route functions ### 2. Schema Derivation ```rust #[derive(Serialize, Schema)] struct Message { greeting: String, } ``` - `Serialize` — Enables JSON serialization - `Schema` — Generates OpenAPI documentation automatically ### 3. Route Macro ```rust #[rustapi_rs::get("/")] async fn hello() -> Json { ... } ``` The `#[rustapi_rs::get]` macro: - Registers the route at compile time - Generates OpenAPI documentation - Works with `RustApi::auto()` for zero-config routing Route macros also work through dependency aliases, e.g. `#[api::get("/")]` when `rustapi-rs` is renamed to `api` in `Cargo.toml`. ### 4. Auto Configuration ```rust RustApi::auto().run("127.0.0.1:8080").await ``` `RustApi::auto()` automatically: - Discovers all `#[rustapi_rs::get/post/...]` routes - Enables Swagger UI at `/docs` - Enables OpenAPI spec at `/openapi.json` --- ## Adding Parameters ### Path Parameters ```rust #[derive(Serialize, Schema)] struct User { id: u64, name: String, } #[rustapi_rs::get("/users/{id}")] async fn get_user(Path(id): Path) -> Json { Json(User { id, name: format!("User {}", id), }) } ``` Test: `curl http://localhost:8080/users/42` ### Query Parameters ```rust #[derive(Deserialize, Schema)] struct Pagination { page: Option, limit: Option, } #[rustapi_rs::get("/users")] async fn list_users(Query(params): Query) -> Json> { let page = params.page.unwrap_or(1); let limit = params.limit.unwrap_or(10); // Fetch users... Json(vec![]) } ``` Test: `curl http://localhost:8080/users?page=2&limit=20` ### Request Body ```rust #[derive(Deserialize, Schema)] struct CreateUser { name: String, email: String, } #[rustapi_rs::post("/users")] async fn create_user(Json(body): Json) -> Json { Json(User { id: 1, name: body.name, }) } ``` Test: ```bash curl -X POST http://localhost:8080/users \ -H "Content-Type: application/json" \ -d '{"name": "Alice", "email": "alice@example.com"}' ``` --- ## Validation Add validation to your requests: ```rust use rustapi_rs::prelude::*; #[derive(Deserialize, Validate, Schema)] struct CreateUser { #[validate(length(min = 3, max = 50))] name: String, #[validate(email)] email: String, #[validate(range(min = 0, max = 150))] age: u8, } #[rustapi_rs::post("/users")] async fn create_user(ValidatedJson(body): ValidatedJson) -> Json { // `body` is guaranteed valid at this point Json(User { id: 1, name: body.name }) } ``` Invalid requests return 422 with detailed errors: ```json { "status": 422, "error_type": "validation_error", "message": "Request validation failed", "fields": [ {"field": "email", "message": "Invalid email format"}, {"field": "age", "message": "Must be between 0 and 150"} ] } ``` ### Available Validators | Validator | Usage | |-----------|-------| | `email` | `#[validate(email)]` | | `length` | `#[validate(length(min = 1, max = 100))]` | | `range` | `#[validate(range(min = 0, max = 999))]` | | `regex` | `#[validate(regex(path = "PHONE_REGEX"))]` | | `url` | `#[validate(url)]` | | `custom` | `#[validate(custom(function = "my_validator"))]` | --- ## Application State Share data across handlers: ```rust use std::sync::Arc; use tokio::sync::RwLock; type Db = Arc>>; #[tokio::main] async fn main() -> Result<(), Box> { let db: Db = Arc::new(RwLock::new(vec![])); RustApi::new() .state(db) .route("/users", get(list_users)) .route("/users", post(create_user)) .run("127.0.0.1:8080") .await } async fn list_users(State(db): State) -> Json> { let users = db.read().await; Json(users.clone()) } async fn create_user( State(db): State, Json(body): Json, ) -> Json { let mut users = db.write().await; let user = User { id: users.len() as u64 + 1, name: body.name }; users.push(user.clone()); Json(user) } ``` --- ## Error Handling RustAPI provides the `Result` type alias: ```rust use rustapi_rs::prelude::*; #[rustapi_rs::get("/users/{id}")] async fn get_user(Path(id): Path) -> Result> { if id == 0 { return Err(ApiError::bad_request("ID cannot be zero")); } let user = find_user(id) .ok_or_else(|| ApiError::not_found(format!("User {} not found", id)))?; Ok(Json(user)) } ``` ### Built-in Error Helpers ```rust ApiError::bad_request("message") // 400 ApiError::unauthorized("message") // 401 ApiError::forbidden("message") // 403 ApiError::not_found("message") // 404 ApiError::conflict("message") // 409 ApiError::unprocessable("message") // 422 ApiError::internal("message") // 500 ``` --- ## Middleware ### CORS ```toml rustapi-rs = { version = "0.2.2", features = ["extras-cors"] } ``` ```rust use rustapi_rs::extras::cors::CorsLayer; RustApi::new() .layer(CorsLayer::permissive()) // Allow all origins // Or configure: // .layer(CorsLayer::new() // .allow_origin("https://example.com") // .allow_methods(["GET", "POST"]) // .allow_headers(["Content-Type"])) .route("/api/data", get(data)) .run("0.0.0.0:8080") .await ``` ### JWT Authentication ```toml rustapi-rs = { version = "0.2.2", features = ["extras-jwt"] } ``` ```rust use rustapi_rs::extras::jwt::{AuthUser, JwtLayer}; #[derive(Serialize, Deserialize)] struct Claims { sub: String, exp: u64, } RustApi::new() .layer(JwtLayer::new("your-secret-key").skip_paths(["/login", "/health"])) .route("/protected", get(protected)) .route("/login", post(login)) .run("0.0.0.0:8080") .await async fn protected(user: AuthUser) -> Json { Json(Response { message: format!("Hello, {}", user.0.sub) }) } ``` ### Rate Limiting ```toml rustapi-rs = { version = "0.2.2", features = ["extras-rate-limit"] } ``` ```rust use rustapi_rs::extras::rate_limit::RateLimitLayer; RustApi::new() .layer(RateLimitLayer::new(100, Duration::from_secs(60))) // 100 req/min .route("/api", get(handler)) .run("0.0.0.0:8080") .await ``` --- ## TOON Format (LLM Optimization) ```toml rustapi-rs = { version = "0.2.2", features = ["protocol-toon"] } ``` ```rust use rustapi_rs::protocol::toon::{Toon, LlmResponse, AcceptHeader}; // Direct TOON response #[rustapi_rs::get("/ai/users")] async fn ai_users() -> Toon { Toon(get_users()) } // Content negotiation based on Accept header #[rustapi_rs::get("/users")] async fn users(accept: AcceptHeader) -> LlmResponse { LlmResponse::new(get_users(), accept.preferred) } ``` Response includes token counting headers: - `X-Token-Count-JSON`: Token count for JSON format - `X-Token-Count-TOON`: Token count for TOON format - `X-Token-Savings`: Percentage saved (e.g., "57.8%") --- ## WebSocket Support Real-time bidirectional communication: ```toml rustapi-rs = { version = "0.2.2", features = ["protocol-ws"] } ``` ```rust use rustapi_rs::protocol::ws::{WebSocket, WebSocketUpgrade, WebSocketStream, Message}; #[rustapi_rs::get("/ws")] async fn websocket(ws: WebSocket) -> WebSocketUpgrade { ws.on_upgrade(handle_connection) } async fn handle_connection(mut stream: WebSocketStream) { while let Some(msg) = stream.recv().await { match msg { Message::Text(text) => { // Echo the message back stream.send(Message::Text(format!("Echo: {}", text))).await.ok(); } Message::Close(_) => break, _ => {} } } } ``` Test with `websocat`: ```bash websocat ws://localhost:8080/ws ``` --- ## Template Engine Server-side HTML rendering with Tera: ```toml rustapi-rs = { version = "0.2.2", features = ["protocol-view"] } ``` Create a template file `templates/index.html`: ```html {{ title }}

Hello, {{ name }}!

``` Use in your handler: ```rust use rustapi_rs::protocol::view::{Templates, View, TemplatesConfig}; #[tokio::main] async fn main() { let templates = Templates::new(TemplatesConfig { directory: "templates".into(), extension: "html".into(), }).unwrap(); RustApi::new() .state(templates) .route("/", get(home)) .run("0.0.0.0:8080") .await } #[derive(Serialize)] struct HomeData { title: String, name: String, } #[rustapi_rs::get("/")] async fn home(templates: Templates) -> View { View::new(&templates, "index.html", HomeData { title: "Welcome".into(), name: "World".into(), }) } ``` --- ## CLI Tool Scaffold new RustAPI projects quickly: ```bash # Install the CLI cargo install cargo-rustapi # Create a new project cargo rustapi new my-api # Interactive mode with template selection cargo rustapi new my-api --interactive # Run with hot-reload (auto-restart on file changes) cargo rustapi watch # Add features or dependencies cargo rustapi add extras-cors extras-jwt # Check environment health cargo rustapi doctor ``` Available templates: - `minimal` — Basic RustAPI setup - `api` — REST API with CRUD operations - `web` — Full web app with templates and WebSocket - `full` — Everything included ### Deploy to RustAPI Cloud Managed hosting via CLI (backend: [RustAPI-Cloud](https://github.com/Tuntii/RustAPI-Cloud)): ```bash cargo rustapi login cargo rustapi whoami cargo rustapi deploy cloud cargo rustapi deploy status ``` Default cloud API: `https://api.rustapi.cloud`. See [RustAPI Cloud recipe](cookbook/src/recipes/rustapi_cloud.md) for auth, self-hosted backends, and troubleshooting. For Docker/Kubernetes/Fly.io, see [Deployment recipe](cookbook/src/recipes/deployment.md). --- ## Testing ```rust use rustapi_rs::test::TestClient; #[tokio::test] async fn test_hello() { let app = RustApi::new() .route("/", get(hello)); let client = TestClient::new(app); let res = client.get("/").send().await; assert_eq!(res.status(), 200); let body: Message = res.json().await; assert_eq!(body.greeting, "Hello, World!"); } #[tokio::test] async fn test_create_user() { let app = RustApi::new() .route("/users", post(create_user)); let client = TestClient::new(app); let res = client .post("/users") .json(&CreateUser { name: "Alice".into(), email: "alice@test.com".into() }) .send() .await; assert_eq!(res.status(), 200); } ``` --- ## Next Steps - [Philosophy](PHILOSOPHY.md) — Understand our design principles - [Architecture](ARCHITECTURE.md) — Deep dive into internals - [Features](FEATURES.md) — Complete feature documentation - [Production Baseline](PRODUCTION_BASELINE.md) — Recommended production defaults - [RustAPI Cloud](cookbook/src/recipes/rustapi_cloud.md) — Managed hosting guide - [Examples](../crates/rustapi-rs/examples/) — In-repo examples --- ## Common Patterns ### Health Check RustAPI now ships standard health probes out of the box: ```rust use rustapi_rs::prelude::*; #[rustapi_rs::main] async fn main() -> std::result::Result<(), Box> { RustApi::auto() .health_endpoints() .run("127.0.0.1:8080") .await } ``` This enables: - `/health` — aggregate health report - `/ready` — readiness probe for orchestrators - `/live` — lightweight liveness probe If you want a stronger production-ready baseline from day one, use: ```rust use rustapi_rs::prelude::*; #[rustapi_rs::main] async fn main() -> std::result::Result<(), Box> { RustApi::auto() .production_defaults("hello-api") .run("127.0.0.1:8080") .await } ``` This installs request IDs, tracing middleware, and probe endpoints together. If you want a custom probe, you can still define one manually: ```rust #[rustapi_rs::get("/health")] async fn health() -> &'static str { "OK" } ``` ### Multiple Response Types ```rust #[rustapi_rs::get("/data")] async fn data() -> Result> { match fetch_data().await { Ok(data) => Ok(Json(data)), Err(_) => Err(ApiError::not_found("Data not found")), } } ``` ### Custom Status Codes ```rust use rustapi_rs::response::Created; #[rustapi_rs::post("/users")] async fn create_user(Json(body): Json) -> Created { let user = User { id: 1, name: body.name }; Created(user) // Returns 201 Created } ``` --- ## Troubleshooting ### "Route not found" for macro-decorated handlers Make sure you're using `RustApi::auto()`: ```rust // ✅ Correct RustApi::auto().run("0.0.0.0:8080").await // ❌ Won't find macro routes RustApi::new().run("0.0.0.0:8080").await ``` ### Compilation errors with extractors Ensure your types implement required traits: ```rust // ✅ For request bodies #[derive(Deserialize)] struct RequestBody { ... } // ✅ For responses #[derive(Serialize)] struct ResponseBody { ... } // ✅ For OpenAPI docs #[derive(Schema)] struct AnyBody { ... } ``` ### Swagger UI not showing Check that `core-openapi` is enabled (it is included in the default `core` feature): ```toml rustapi-rs = { version = "0.2.2", features = ["core-openapi"] } ``` ### CLI Commands Not Working Use the new `doctor` command to diagnose: ```bash cargo rustapi doctor ``` --- Happy coding! 🦀