1. Install
Unique.js requires Rust 1.96+ for the HTTP engine. Install it from rustup.rs if you have not already. Then create a new project:
# Install Rust (one-time setup)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Create a new Unique.js project
cargo new myapp
cd myappAdd Unique.js to your Cargo.toml:
[dependencies]
unique = "1"
unique-core = "1"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"2. Your First App
Open src/main.rs and replace the contents with:
use unique::Unique;
#[tokio::main]
async fn main() {
Unique::new()
.handle_get("/", |_req, res| res.text("Hello, World!"))
.handle_get("/api/health", |_req, res| {
res.json(r#"{"status":"ok"}"#)
})
.run("0.0.0.0:3000")
.await
.unwrap();
}This creates a Unique.js app with two routes: a homepage that returns plain text, and a health check endpoint that returns JSON. Security headers, CORS, rate limiting, and logging are all on by default.
3. Run It
cargo runOpen http://localhost:3000 in your browser. You should see "Hello, World!". Visit/api/health for the JSON endpoint.
Try the auto-generated API docs at/docs — you will see a Swagger UI listing both routes. No annotations needed.
4. Add Routes
Add path parameters, wildcards, and POST handlers:
Unique::new()
// Path parameter: /users/42
.handle_get("/users/:id", |req, res| {
let id = req.param("id").unwrap_or("0");
res.text(format!("User {}", id))
})
// Wildcard: /assets/css/app.css
.handle_get("/assets/*path", |req, res| {
let path = req.param("path").unwrap_or("");
res.text(format!("File: {}", path))
})
// POST with JSON body
.handle_post("/api/echo", |req, res| {
// Echo the request body back
res.header("content-type", "application/json")
.text(&req.body)
})
.run("0.0.0.0:3000").await.unwrap();5. Add a Database
Add the ORM to your Cargo.toml:
unique-orm = { version = "1", features = ["sqlite"] }
unique-macros = "1"Define a model and CRUD routes:
use unique_orm::{Db, DbConfig};
use unique_macros::Model;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Model)]
#[table(name = "todos")]
struct Todo {
#[field(primary, auto_increment)]
id: i64,
title: String,
#[field(default = "false")]
done: bool,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db = Db::connect(DbConfig {
url: "sqlite://todos.db".into(),
max_connections: 5,
min_connections: 1,
}).await?;
// Auto-create the table
db.migrate(&[Todo::create_table_sql()]).await?;
Unique::new()
.handle_get("/api/todos", move |_req, res| {
let db = db.clone();
Box::pin(async move {
let todos = Todo::all(&db).await.unwrap_or_default();
res.json(serde_json::to_string(&todos).unwrap())
})
})
.run("0.0.0.0:3000").await?;
Ok(())
}6. Deploy
Build a production binary:
cargo build --release
# Binary is at target/release/myappOr generate Docker + systemd configs:
# Generate Dockerfile + docker-compose.yml
unique deploy --target docker
# Generate systemd service file
unique deploy --target systemdFor production, put Unique.js behind a reverse proxy (nginx or Caddy) for TLS termination. See the FAQ for deployment details.
Next Steps
- Read the full 50-chapter tutorial for deep dives into every feature
- Browse the API Reference for complete method documentation
- Copy a ready-to-run example for WebSocket, JWT auth, file upload, and more
- Check the FAQ if you get stuck