Bring your services together.
Define dependencies and lifetimes. The container creates services, resolves their dependencies and cleans them up when their lifetimes end.
Let Lily manage dependencies and lifecycle. Build on the same foundation, from HTTP APIs to real-time connections.
Typed APIs Built-in DI Managed lifecycle
An HTTP request, a WebSocket message or a queued job. At the heart of your application: the same dependency model and explicit resource ownership.
Define dependencies and lifetimes. The container creates services, resolves their dependencies and cleans them up when their lifetimes end.
Lily owns the application lifecycle. Preparation, execution and shutdown each have clear responsibilities.
The container, controllers and policies are prepared.
The listener opens; requests or messages are accepted.
New work stops; managed resources are awaited as they close.
Choose your entry point. Follow the examples step by step, from your first application to advanced topics.
lily_http_apiBuild your API with controllers and typed requests and responses. Connect security and data access to the same application.
lily_websocketManage connections, messages and room broadcasts. Build real-time communication with typed controllers.
lily_consumerProcess RabbitMQ messages with typed handlers. Manage queues, transactions and shutdown through one application lifecycle.
Choose your project, run the commands and paste the code into src/main.rs. Get your first response right away.
One endpoint, one Rust file. Get your first HTTP response.
Rust 1.96.1+ · Commands are for Bash / Zsh.cargo new --bin --edition 2024 api
cd api
cargo add lilyrs@0.1.0 --features http-api
cargo add tokio@1 --features macros,rt-multi-threadReplace the entire contents of src/main.rs with the code below.
use std::sync::Arc;
use lilyrs::http_api::{
controller, AppBuilder, Controller, ControllerInitError, ControllerTrait,
Extensions, HttpApiError,
};
#[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<String, HttpApiError> {
Ok("Hello, Lily!".into())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
AppBuilder::new("127.0.0.1:8080")
.tls_disabled()
.build().await?
.start().await?;
Ok(())
}Run this in the project directory. Press Ctrl+C to stop the application.
cargo runRun this in another terminal.
curl http://127.0.0.1:8080/api/helloThe response contains Hello, Lily!
Send a message and get it back. The entire server fits in one Rust file.
Rust 1.96.1+ · Commands are for Bash / Zsh.cargo new --bin --edition 2024 websocket
cd websocket
cargo add lilyrs@0.1.0 --features websocket
cargo add tokio@1 --features macros,rt-multi-threadReplace the entire contents of src/main.rs with the code below.
use std::sync::Arc;
use lilyrs::websocket::{
async_trait, websocket_controller, Emit, Extensions, Payload, ServerConfig,
WebSocketActionError, WebSocketController, WebSocketControllerInitError,
WebSocketControllerTrait, WsAppBuilder,
};
#[derive(WebSocketController)]
#[namespace("hello")]
struct HelloController;
#[async_trait]
impl WebSocketControllerTrait for HelloController {
async fn new(_: Arc<Extensions>) -> Result<Self, WebSocketControllerInitError> {
Ok(Self)
}
}
#[websocket_controller]
impl HelloController {
#[message("echo")]
async fn echo(&self, Payload(text): Payload<String>)
-> Result<Emit<String>, WebSocketActionError>
{
Emit::new("hello:echoed", text).map_err(WebSocketActionError::internal)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
WsAppBuilder::new("127.0.0.1:8081")
.config(ServerConfig {
allow_missing_origin: true,
require_subprotocol: true,
..ServerConfig::default()
})
.build().await?
.start().await?;
Ok(())
}Run this in the project directory. Press Ctrl+C to stop the application.
cargo runRun this in another terminal with Node.js 22 or later. This client sends no Origin, so the server enables allow_missing_origin; the lily.v2 subprotocol is required.
node --input-type=module -e '
const socket = new WebSocket("ws://127.0.0.1:8081/ws?namespace=hello", "lily.v2");
socket.addEventListener("open", () => socket.send(JSON.stringify({
protocol_version: 2, msg_type: "event", event: "hello:echo",
content_kind: "json", content_type: "application/json",
encoding: "identity", data: "Hello, Lily!", timestamp: Date.now()
})));
socket.addEventListener("message", ({ data }) => {
console.log(JSON.parse(data));
socket.close();
});
'The response has event: hello:echoed and data: Hello, Lily!
Print a queue message to the terminal. The worker is a single Rust file; Docker is required for local RabbitMQ.
Rust 1.96.1+ · Commands are for Bash / Zsh.cargo new --bin --edition 2024 consumer
cd consumer
cargo add lilyrs@0.1.0 --features consumer
cargo add tokio@1 --features macros,rt-multi-threadWith Docker running, run these commands once. AMQP and management ports are exposed only on your local machine. The second command waits until RabbitMQ is ready.
docker run --detach --rm --name lily-quickstart-rabbitmq \
-p 127.0.0.1:5672:5672 -p 127.0.0.1:15672:15672 \
rabbitmq:4.3.4-management
curl --retry 30 --retry-all-errors --retry-delay 1 --fail \
--user guest:guest \
http://127.0.0.1:15672/api/health/checks/ready-to-serve-clientsRun this in the Consumer project directory. It creates lily.toml with the connection and queue settings.
cat > lily.toml <<'TOML'
[rabbitmq.consumer]
hostname = "127.0.0.1"
port = 5672
username = "guest"
password = "guest"
vhost = "/"
use_tls = false
pool_size = 1
[[rabbitmq.topology.queues]]
name = "hello.messages"
exchange_name = "hello"
routing_key = "hello.messages"
concurrency = 1
prefetch_count = 10
[rabbitmq.topology.queues.retention]
main_max_messages = 1000
main_max_bytes = 10485760
retry_bucket_max_messages = 1000
retry_bucket_max_bytes = 10485760
dead_letter_max_messages = 1000
dead_letter_max_bytes = 10485760
TOMLReplace the entire contents of src/main.rs with the code below.
use lilyrs::{
consumer::{Consumer, ConsumerError, Injectable, ServiceTrait},
queue::{Json, QueueHandlerError, queue, queue_service},
};
#[derive(Default, Injectable)]
#[service(lifetime = "Singleton")]
struct HelloWorker;
impl ServiceTrait for HelloWorker {}
#[queue_service]
impl HelloWorker {
#[queue("hello.messages", version = 1, content = "json")]
async fn hello(&self, Json(text): Json<String>) -> Result<(), QueueHandlerError> {
println!("{text}");
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), ConsumerError> {
Consumer::run().await
}Run this in the project directory. Press Ctrl+C to stop the application.
cargo runOnce the Consumer has started, run this in another terminal. This trial uses RabbitMQ’s management API; use the guide’s QueueClientService example to publish from your application.
curl --fail-with-body --user guest:guest \
--header 'Content-Type: application/json' \
--request POST http://127.0.0.1:15672/api/exchanges/%2F/hello/publish \
--data '{
"properties": {
"content_type": "application/json",
"headers": {
"x-lily-event-id": "11111111-1111-4111-8111-111111111111",
"x-lily-schema-version": "1",
"x-lily-content-kind": "json"
}
},
"routing_key": "hello.messages",
"payload": "\"Hello, Lily!\"",
"payload_encoding": "string"
}'curl returns routed: true; the Consumer terminal prints Hello, Lily!
First stop the Consumer with Ctrl+C, then stop the example’s RabbitMQ container.
docker stop lily-quickstart-rabbitmqFrom databases to security, file transfers to observability. Find setup instructions and usage limits for each topic in the guides.
Manage connections and data access through DI.
PostgreSQL MongoDB Cache and HTTP clientConnect identity and access rules to your pipeline.
Authentication CORS CSRFDefine contracts and data transfer.
OpenAPI File uploads Streaming and filesLimit traffic and observe your running application.
Rate limiting Observability WebSocket shutdown
YOUR NEXT STEPFrom setup to your first response. Get to know Lily by writing code.
One package. The components you need. lilyrs 0.1.0
cargo add lilyrs@0.1.0 --features http-apiChoose http-api for HTTP APIs, websocket for real-time connections, or consumer for message processing.