Your first HTTP application
Define a controller, return a JSON response, and run your Lily application.
By the end of this page, you will have an application that returns JSON for GET /api/hello. The example runs over local HTTP.
Prepare the project#
lilyrs 0.1.0 is published on crates.io. The framework's manifest minimum and validated toolchain are Rust 1.96.1, with edition 2024. Create your application as an independent Cargo project:
cargo new hello-lilyUpdate hello-lily/Cargo.toml as follows:
[package]
name = "hello-lily"
version = "0.1.0"
edition = "2024"
rust-version = "1.96.1"
[dependencies]
lilyrs = { version = "0.1.0", features = ["http-api"] }
serde = { version = "1", features = ["derive"] }lilyrs enables no components by default. The http-api feature exposes the HTTP API under lilyrs::http_api. Controllers, middleware, guards, and DI types share this entry point; you do not need separate HTTP macro, derive, or registry dependencies. The DI guide explains how to define services.
When adding an integration, extend the feature list on the same lilyrs dependency, for example features = ["http-api", "postgresql"]. The feature is spelled http-api, while the Rust module is http_api. The facade re-exports the existing components; application lifecycle ownership and canonical API calls remain the same.
Controller and application#
Replace src/main.rs with the following:
use std::sync::Arc;
use lilyrs::http_api::{
controller, AppBuilder, Controller, ControllerInitError, ControllerTrait,
Extensions, HttpApiError,
};
use serde::Serialize;
#[derive(Serialize)]
struct Hello { message: &'static str }
#[derive(Controller)]
#[base_path("/api")]
struct HelloController;
#[lilyrs::http_api::async_trait::async_trait]
impl ControllerTrait for HelloController {
async fn new(_: Arc<Extensions>) -> Result<Self, ControllerInitError> {
Ok(Self)
}
}
#[controller]
impl HelloController {
#[get("/hello")]
async fn hello(&self) -> Result<Hello, HttpApiError> {
Ok(Hello { message: "Merhaba Lily" })
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
lilyrs::http_api::tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?
.block_on(async {
AppBuilder::new("127.0.0.1:8080")
.tls_disabled()
.build().await?
.start().await?;
Ok::<(), Box<dyn std::error::Error>>(())
})
}#[base_path("/api")] sets the controller's shared path. Combined with #[get("/hello")], it produces /api/hello. Result<Hello, HttpApiError> converts the successful value to JSON. Controllers are discovered at link time; there is no add_controller call.
Run the application#
cd hello-lily
cargo runIn another terminal:
curl -i http://127.0.0.1:8080/api/helloA successful response has status 200 and the following body:
{"message":"Merhaba Lily"}Shut down the application#
Ctrl+C initiates shutdown. start().await returns when shutdown operations have completed. Every successful build must complete a start or close path; dropping the App value does not replace async cleanup.
Choosing Tokio. The example builds the runtime through the HTTP facade. If you prefer
#[tokio::main], add a directtokiodependency to your application with themacrosand runtime features.
Sources for this page v0.1.0
This guide is based on the reviewed framework commit.