High-fidelity engineering for the modern virtuoso.
At peak optimization, Chopin delivers industry-leading throughput, effectively outperforming Actix Web and Axum while maintaining significantly lower latency.
chopin new my_project
Core Architecture
Shared-Nothing Model
Strict shared-nothing model ensures linear scaling across multi-core systems. Each CPU core runs its own isolated event loop, memory allocator, and metrics counters.
- SO_REUSEPORT: Kernel balances connections, eliminating "Acceptor" thread bottlenecks.
- Partitioned Metrics: Cache-local atomic counters prevent cache-line bouncing.
Zero-Allocation Pipeline
Radical memory efficiency with a pipeline that slices raw socket buffers into string references without a single heap allocation.
- Stack-Allocated Hot-Paths: HTTP headers stored in fixed-size arrays.
- Raw Byte Serialization: Inline
itoaformatting removesstd::fmtoverhead.
Native Asynchronous Core
Platform native asynchronous I/O with direct interaction with the underlying OS kernels for maximum efficiency.
- Platform Native: Direct
kqueue(macOS) andepoll(Linux) usage. - Connection Slab: Custom O(1) connection state management (10,000 slots/worker).
- TCP_NODELAY: Inherited from listener to save syscalls.
Performance Features
Core Affinity
Workers are pinned to logical CPU cores using core_affinity to maximize
cache locality and minimize context switching.
O(1) Slab Allocator
Memory for connections is pre-allocated. Reclaiming a connection handle is a simple index lookup, preventing heap fragmentation.
Catch-Unwind
Global panic hooks ensure that a single failing handler doesn't crash the entire worker thread, maintaining 99.999% availability.
writev Zero-Copy Flush
Response headers and body are delivered in a single writev syscall. Static
bodies bypass the write buffer entirely — no memcpy.
sendfile File Serving
Response::file() uses the platform sendfile to transfer file
bytes entirely in kernel space, eliminating user-space copies.
Pre-Composed Middleware
Middleware chains are resolved once at startup. The hot path calls one pre-built
Arc<dyn Fn> — zero Arc::new per request.
mimalloc Global Allocator
Per-thread free lists and low lock-contention allocation via mimalloc
dramatically reduces allocation latency under concurrency.
Technical Specification
Chopin is engineered for maximum compatibility with modern web standards and database protocols.
| Protocol/Feature | Implementation Level | Technical Lead |
|---|---|---|
| HTTP/1.1 | Production Ready | Zero-copy parsing; writev header+body flush; HTTP pipelining; configurable pipeline depth. |
| Static Files + Range Requests | Zero-Copy (sendfile) |
Kernel-space file transfer; RFC 7233 range requests (206 Partial Content); auto MIME detection (~30 types). |
| TLS/HTTPS | rustls (1.2/1.3) | In-worker TLS via tls feature; Chopin::serve_tls(addr, cert, key); AWS ACM CA bundle support. |
| WebSocket (RFC 6455) | Native | Upgrade handshake, frame codec (text/binary/ping/pong/close), fragmentation handling. |
| PostgreSQL | Native (Port 5432) | Extended Query Protocol; SASL/SCRAM-SHA-256 + MD5 auth; TLS/SSL; COPY IN/OUT; LISTEN/NOTIFY; savepoints. |
| JSON (RFC 8259) | Schema-JIT | Near-memory-bandwidth serialization via kowito-json. |
| JWT (RFC 7519) | Zero-Allocation | HS256/RS256/ES256; role & scope middleware macros; token revocation; Argon2id password hashing; OAuth PKCE. |
| Multipart (RFC 7578) | Streaming | Iterative parsing of form-data without whole-body buffering. |
| Compression | gzip (compression feature) |
Response::gzip() via flate2; automatic Content-Encoding: gzip header. |
| Observability | Prometheus + Structured Logging | Chopin::with_metrics(path) Prometheus endpoint; with_health(path) k8s probe; JSON logging via logging feature. |
Performance Benchmark
Results below represent single-core performance on macOS Apple Silicon. Chopin is 40-43% faster than Hyper with 5.4x lower latency.
| Framework | Endpoint | Relative Throughput | Latency (Avg) |
|---|---|---|---|
| Chopin | /json |
100% | 686 μs |
| Chopin | /plain |
100% | 700 μs |
| Actix Web | /json |
91% | 812 μs |
| Axum | /json |
84% | 945 μs |
| Hyper | /json |
73% | 1,810 μs |
| Hyper | /plain |
73% | 1,820 μs |
Throughput Comparison
-------------------------------------------------------
Chopin [██████████████████████████████] 100% (Baseline)
Actix Web [███████████████████████████ ] 91%
Axum [█████████████████████████ ] 84%
Hyper [██████████████████████ ] 73%
-------------------------------------------------------
"Simple as a melody, fast as a nocturne." - nocturne-op9-no2
Installation
The easiest way to start is using the Chopin CLI. Install it via cargo:
cargo install chopin-cli
Or add individual crates to your Cargo.toml:
[dependencies]
chopin-core = "0.5.27"
chopin-macros = "0.5.27" # For attribute routing
chopin-pg = "0.5.27" # High-performance PostgreSQL driver
chopin-orm = "0.5.27" # Zero-allocation ORM layer
kowito-json = "0.2.18" # Schema-JIT SIMD JSON serializer
CLI Reference
The chopin CLI provides a full suite of tools for rapid development.
| Command | Description |
|---|---|
chopin new <name> |
Create a new Chopin project scaffold. |
chopin dev |
Start the development server with hot-reload. |
chopin build |
Build the project for production (release profile). |
chopin generate app <name> |
Scaffold a new module (models, handlers, errors). |
chopin generate handler <app> <name> |
Add a new handler to an existing app. |
chopin check |
Run the architectural linter to ensure best practices. |
chopin deploy docker |
Generate a production-optimized Dockerfile. |
chopin openapi |
Generate OpenAPI 3.0 specs from your routes. |
chopin db shell |
Open an interactive PostgreSQL shell. |
chopin db dump --file dump.sql |
Dump production database data to a file. |
chopin db restore --file dump.sql |
Restore database from a dump file. |
chopin generate model <name> [field:type …] |
Scaffold a #[derive(Model)] struct + timestamped SQL migration files. |
chopin migrate up/down |
Run pending SQL migrations up or roll back the last applied migration. |
Quick Start
Looking for a full tutorial?
Check out our comprehensive step-by-step API Developer Guide to learn everything from basic routing to advanced DB connection pooling.
Modern Chopin uses attribute macros for routing and implicit discovery.
use chopin_core::{Context, Response};
use chopin_macros::get;
#[get("/")]
fn hello_world(_ctx: Context) -> Response {
Response::text("Hello, World!")
}
fn main() {
// Automatically discovers all routes marked with macros
Chopin::new()
.mount_all_routes()
.serve("0.0.0.0:8080")
.unwrap();
}
Routing & Macros
Chopin supports both declarative attribute macros and manual router configuration. Both use the same underlying Radix Tree for $O(K)$ matching.
Attribute Macros
#[get("/users")]
fn list_users(ctx: Context) -> Response { ... }
#[post("/users")]
fn create_user(ctx: Context) -> Response { ... }
#[get("/user/:id")]
fn get_user(ctx: Context) -> Response {
let id = ctx.param("id");
...
}
Manual Routing
let mut router = Router::new();
router.get("/about", about_handler);
router.get("/static/*path", static_file_handler);
Extractors & JSON
Chopin uses kowito-json for "Schema-JIT" serialization. Serialization logic is
generated at compile time — JSON encoding runs at near-memory bandwidth speeds. Deserialization uses
serde.
Serializing Responses
use chopin_core::{Context, Response};
use kowito_json::serialize::Serialize;
#[derive(Serialize)]
struct User {
id: u64,
username: String,
is_active: bool,
}
#[get("/user/:id")]
fn get_user(ctx: Context) -> Response {
let user = User { id: 42, username: "virtuoso".into(), is_active: true };
ctx.json(&user) // or Response::json(&user)
}
Deserializing Request Bodies (Json<T>)
use serde::Deserialize;
use chopin_core::extract::Json;
#[derive(Deserialize)]
struct CreateUser { username: String, email: String }
#[post("/users")]
fn create_user(ctx: Context) -> Response {
let Json(body) = match ctx.extract::<Json<CreateUser>>() {
Ok(j) => j,
Err(e) => return e,
};
// body.username and body.email are ready to use
Response::new(201)
}
Query Parameters (Query<T>)
use serde::Deserialize;
use chopin_core::extract::Query;
#[derive(Deserialize)]
struct SearchParams { q: String, page: Option<u32> }
#[get("/search")]
fn search(ctx: Context) -> Response {
let Query(params) = match ctx.extract::<Query<SearchParams>>() {
Ok(q) => q,
Err(e) => return e,
};
Response::text(format!("Searching: {}", params.q))
}
Zero-Copy File Serving
Use Response::file(path) to serve files with zero user-space copying. The worker passes
the file descriptor directly to the kernel via sendfile (Linux) / sendfile
(macOS). Content-Type is inferred from the extension automatically (∼30 MIME types).
Automatic Detection
#[get("/assets/*path")]
fn serve_asset(ctx: Context) -> Response {
let path = ctx.param("path").unwrap_or("");
// Automatically sets Content-Type. Returns 404 if not found.
Response::file(&format!("public/{}", path))
}
Custom Range / Content-Type
use chopin_core::http::OwnedFd;
use chopin_core::syscalls::{open_file_readonly, file_size};
#[get("/download")]
fn download(ctx: Context) -> Response {
let fd = open_file_readonly("/data/archive.tar.gz").unwrap();
let size = file_size(fd).unwrap();
// Response::sendfile(fd, offset, len, content_type)
Response::sendfile(OwnedFd::new(fd), 0, size, "application/gzip")
}
| Extension | Content-Type | Extension | Content-Type |
|---|---|---|---|
| .html | text/html | .json | application/json |
| .js | application/javascript | .css | text/css |
| .png | image/png | .jpg/.jpeg | image/jpeg |
| .svg | image/svg+xml | .webp | image/webp |
| .wasm | application/wasm | .gz | application/gzip |
| application/pdf | .mp4 | video/mp4 |
Response API Reference
Every handler returns a Response. All constructors return a 200-status response unless
noted. Use .with_header(name, value) to chain custom headers.
| Method | Status | Body / Behaviour |
|---|---|---|
Response::text(body) | 200 | Allocates a byte body with text/plain content-type. |
Response::text_static(body) | 200 | Zero-alloc. Takes &'static [u8]. Ideal for fixed messages. |
Response::json(&val) | 200 | Serializes T: Serialize via Schema-JIT engine. |
Response::json_bytes(body) | 200 | Pre-serialized JSON bytes. Sets application/json. |
Response::json_static(body) | 200 | Zero-alloc. Takes &'static [u8] for pre-serialized JSON known at compile time. |
Response::raw(bytes) | 200 | Ultra-fast. Writes a complete pre-baked HTTP/1.1 response verbatim — bypasses all header serialization. |
Response::stream(iter) | 200 | Chunked streaming body from an iterator of Vec<u8>. |
Response::file(path) | 200/404 | Zero-copy sendfile. Auto Content-Type. Returns 404 on missing file. |
Response::file_range(path, range) | 200/206/416 | Serves file with RFC 7233 Range support. Pass ctx.header("range"). |
Response::sendfile(fd, offset, len, ct) | 200 | Custom range sendfile with explicit content-type. |
Response::new(status) | any | Empty body with given status code. |
Response::not_found() | 404 | Plain-text "Not Found". |
Response::bad_request() | 400 | Plain-text "Bad Request". |
Response::unauthorized() | 401 | Plain-text "Unauthorized". |
Response::forbidden() | 403 | Plain-text "Forbidden". |
Response::server_error() | 500 | Plain-text "Internal Server Error". |
Adding Custom Headers
// Builder style — chains on a value
Response::json(&data)
.with_header("X-Request-Id", request_id)
.with_header("Cache-Control", "no-store")
// Direct mutation in middleware
fn cors_middleware(ctx: Context, next: BoxedHandler) -> Response {
next(ctx).with_header("Access-Control-Allow-Origin", "*")
}
Context Helpers
// Path parameter
let id: &str = ctx.param("id").unwrap_or("0");
// Request header (case-insensitive)
let auth: Option<&str> = ctx.header("Authorization");
// Raw query string (e.g. "page=2&limit=50")
let qs: Option<&str> = ctx.req.query;
// Raw request body bytes
let body: &[u8] = ctx.req.body;
// HTTP method and path
let method = ctx.req.method; // Method::Get / Post / Put / Delete
let path = ctx.req.path; // &str
Multipart & File Uploads
Chopin includes a zero-allocation Multipart parser that operates directly on the request
buffer. Use ctx.multipart() — it automatically reads the Content-Type
boundary. No imports needed.
#[post("/upload")]
fn upload_handler(ctx: Context) -> Response {
let Some(multipart) = ctx.multipart() else {
return Response::bad_request();
};
for part in multipart {
let p = part.unwrap();
println!("Field: {}, File: {:?}, Size: {} bytes",
p.name.unwrap_or("unnamed"),
p.filename,
p.body.len()); // p.body is a &[u8] — zero-copy into the read buffer
}
Response::text("Upload complete")
}
Each Part contains a &[u8] slice pointing directly into the connection
read buffer — no heap allocation, no copy.
Middleware & Authentication
chopin-auth provides JWT issuing/verification, request extraction,
role-based middleware, token revocation, configurable auth errors, and Argon2 password hashing.
This section walks zero-to-hero so you can use every public feature correctly.
0) Add dependency
[dependencies]
chopin-auth = "0.5.16"
serde = { version = "1", features = ["derive"] }
1) Define claims (+ optional JTI for revocation)
use chopin_auth::HasJti;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
role: String,
jti: String,
exp: u64,
}
impl HasJti for Claims {
fn jti(&self) -> Option<&str> { Some(&self.jti) }
}
2) Initialize global JWT manager (once at startup)
init_jwt_manager(...) must be called once before serving requests.
Calling it more than once will panic.
use chopin_auth::{init_jwt_manager, JwtManager, TokenBlacklist};
use std::time::Duration;
fn init_auth() {
let blacklist = TokenBlacklist::new();
blacklist.start_cleanup_task(Duration::from_secs(300));
let manager = JwtManager::new(b"super-secret")
.with_blacklist(blacklist.clone());
init_jwt_manager(manager);
}
3) JWT manager modes (HS256 / RS256 / ES256)
use chopin_auth::JwtManager;
// HS256 sign + verify (shared secret)
let _hs = JwtManager::new(b"secret");
// HS256 verify-only (encode() returns EncodingKeyMissing)
let _hs_verify = JwtManager::verify_only(b"secret");
// RS256 sign + verify
let _rs = JwtManager::from_rsa_pem(PRIVATE_PEM, PUBLIC_PEM)?;
// RS256 verify-only
let _rs_verify = JwtManager::from_rsa_public_pem(PUBLIC_PEM)?;
// ES256 sign + verify
let _es = JwtManager::from_ec_pem(EC_PRIVATE_PEM, EC_PUBLIC_PEM)?;
// ES256 verify-only
let _es_verify = JwtManager::from_ec_public_pem(EC_PUBLIC_PEM)?;
// Advanced: use JwtManager::with_config(JwtConfig { ... }) for full validation/key control.
4) Issue + verify tokens
use chopin_auth::{AuthError, JwtManager};
let manager = JwtManager::new(b"secret");
let claims = Claims {
sub: "user-1".into(),
role: "admin".into(),
jti: "jti-123".into(),
exp: 4_102_444_800, // 2100-01-01 UTC
};
let token = manager.encode(&claims)?;
let decoded: Claims = manager.decode(&token)?;
match manager.decode::<Claims>("bad.token") {
Ok(_) => {}
Err(AuthError::Expired) => eprintln!("expired"),
Err(AuthError::Revoked) => eprintln!("revoked"),
Err(AuthError::InvalidToken(e)) => eprintln!("invalid: {}", e),
Err(e) => eprintln!("other auth error: {}", e),
}
5) Protect handlers with the Auth<T> extractor
The extractor validates Authorization: Bearer ... using the global manager and returns
decoded claims. Default failures: 401 for token issues, 500 if manager is not initialized.
use chopin_auth::Auth;
use chopin_core::{Context, Response};
#[get("/me")]
fn me(ctx: Context) -> Response {
let auth = match ctx.extract::<Auth<Claims>>() {
Ok(a) => a,
Err(resp) => return resp,
};
Response::text(format!("hello {}", auth.claims.sub))
}
6) Role-based middleware (RBAC)
Important: there is no derive macro for Role; implement
Role and RoleCheck manually.
use chopin_auth::{Role, RoleCheck, require_role_middleware};
#[derive(Debug, PartialEq)]
enum UserRole { Admin, User }
impl Role for UserRole {}
impl RoleCheck<UserRole> for Claims {
fn has_role(&self, role: &UserRole) -> bool {
matches!((&*self.role, role), ("admin", UserRole::Admin) | ("user", UserRole::User))
}
}
require_role_middleware!(admin_only, Claims, UserRole::Admin, Claims::has_role);
// Use `admin_only` in your router/middleware chain for admin endpoints.
7) Token revocation (logout / emergency invalidation)
use chopin_auth::TokenBlacklist;
let blacklist = TokenBlacklist::new();
// revoke until token expiry (recommended)
blacklist.revoke("jti-123".to_string(), Some(4_102_444_800));
// checks
let revoked = blacklist.is_revoked("jti-123");
println!("revoked={}", revoked);
// maintenance
blacklist.cleanup();
println!("entries={} empty={}", blacklist.len(), blacklist.is_empty());
8) Custom auth error responses
use chopin_auth::{set_error_handler, AuthError, ErrorHandler};
use chopin_core::Response;
struct JsonAuthErrors;
impl ErrorHandler for JsonAuthErrors {
fn handle(&self, err: AuthError) -> Response {
match err {
AuthError::Expired => Response::new(401),
AuthError::Revoked => Response::new(401),
AuthError::InvalidToken(_) => Response::new(401),
_ => Response::server_error(),
}
}
}
// call once at startup
set_error_handler(JsonAuthErrors);
9) Password hashing (Argon2id)
use chopin_auth::{PasswordHasher, hash_password, verify_password};
// Presets
let interactive = PasswordHasher::interactive(); // login default
let sensitive = PasswordHasher::sensitive(); // stronger / slower
// Custom params: memory_kib, iterations, parallelism
let custom = PasswordHasher::custom(32_768, 3, 1)?;
let hash = interactive.hash(b"my-password")?;
assert!(interactive.verify(b"my-password", &hash)?);
// Convenience helpers
let h2 = hash_password(b"another-password")?;
assert!(verify_password(b"another-password", &h2)?);
assert!(!verify_password(b"wrong", &h2)?);
Zero → Hero checklist: define claims + HasJti, initialize
JwtManager once, protect routes with Auth<Claims>, add RBAC middleware,
enable blacklist revocation, customize error handling, and hash passwords with Argon2 presets/custom params.
Persistence Deep-Dive
chopin-pg is built for Chopin's Shared-Nothing model: each worker owns
its own pool and connections. This section walks from first query to advanced features so you can
use the full driver safely and efficiently.
0) Install & connect
use chopin_pg::{PgConfig, PgConnection, PgResult};
fn main() -> PgResult<()> {
// URL form
let config = PgConfig::from_url("postgres://chopin:chopin@127.0.0.1:5432/postgres")?;
// Builder form
let _config2 = PgConfig::new("127.0.0.1", 5432, "chopin", "chopin", "postgres");
// Unix socket form (macOS/Linux)
let _config3 = PgConfig::new("localhost", 5432, "chopin", "chopin", "postgres")
.with_socket_dir("/var/run/postgresql");
let mut conn = PgConnection::connect(&config)?;
let rows = conn.query_simple("SELECT 1")?;
let one: i32 = rows[0].get_typed(0)?;
assert_eq!(one, 1);
Ok(())
}
1) Worker-local pool (recommended in handlers)
Important: query methods are on PgConnection/ConnectionGuard,
not directly on PgPool. Get a guard first via pool.get() or
pool.try_get().
use chopin_pg::{PgConfig, PgPool};
use std::cell::RefCell;
thread_local! {
pub static DB: RefCell<PgPool> = RefCell::new(
PgPool::connect(
PgConfig::from_url("postgres://chopin:chopin@127.0.0.1:5432/postgres").unwrap(),
10,
).expect("DB connect failed")
);
}
#[get("/users/:id")]
fn get_user(ctx: Context) -> Response {
let id: i32 = ctx.param("id").unwrap_or("1").parse().unwrap_or(1);
DB.with(|db| {
let mut pool = db.borrow_mut();
let mut conn = pool.get().unwrap();
let row = conn
.query_one("SELECT id, name FROM users WHERE id = $1", &[&id])
.unwrap();
let id: i32 = row.get_typed(0).unwrap();
let name: String = row.get_typed(1).unwrap();
Response::text(format!("{}:{}", id, name))
})
}
2) Query API (core CRUD)
let mut conn = PgConnection::connect(&config)?;
// No parameters (simple protocol)
let _rows = conn.query_simple("SELECT current_database()")?;
// Multi-row
let rows = conn.query("SELECT id FROM users WHERE active = $1", &[&true])?;
// Exactly one row (Err(PgError::NoRows) if none)
let row = conn.query_one("SELECT email FROM users WHERE id = $1", &[&42i32])?;
// Optional row
let maybe = conn.query_opt("SELECT email FROM users WHERE id = $1", &[&9999i32])?;
// Execute returns affected rows
let changed = conn.execute("UPDATE users SET active = $1 WHERE id = $2", &[&false, &42i32])?;
println!("updated={}", changed);
3) Transactions, savepoints, nesting
// Closure transaction (auto BEGIN/COMMIT/ROLLBACK)
conn.transaction(|tx| {
tx.execute("INSERT INTO users (name) VALUES ($1)", &[&"Alice"])?;
// Nested transaction => SAVEPOINT
tx.transaction(|nested| {
nested.execute("INSERT INTO logs (msg) VALUES ($1)", &[&"created Alice"])?;
Ok(())
})?;
Ok(())
})?;
// Manual control
conn.begin()?;
conn.savepoint("sp1")?;
conn.execute("UPDATE accounts SET balance = balance - 100 WHERE id = $1", &[&1i32])?;
conn.rollback_to("sp1")?;
conn.release_savepoint("sp1")?;
conn.commit()?;
4) COPY IN / COPY OUT (bulk data)
// COPY IN
let mut writer = conn.copy_in("COPY users (name, email) FROM STDIN")?;
writer.write_row(&["Alice", "alice@example.com"])?;
writer.write_row(&["Bob", "bob@example.com"])?;
let copied = writer.finish()?;
println!("copied rows={}", copied);
// COPY IN with failure path
let writer = conn.copy_in("COPY users (name) FROM STDIN")?;
writer.fail("invalid source format")?;
// COPY OUT
let mut reader = conn.copy_out("COPY users TO STDOUT")?;
let bytes = reader.read_all()?;
println!("{}", String::from_utf8_lossy(&bytes));
5) LISTEN / NOTIFY
conn.listen("events")?;
conn.notify("events", "hello")?;
if let Some(n) = conn.poll_notification()? {
println!("pid={} channel={} payload={}", n.process_id, n.channel, n.payload);
}
for n in conn.drain_notifications() {
println!("{}: {}", n.channel, n.payload);
}
conn.unlisten("events")?;
conn.unlisten_all()?;
6) Pool tuning and health
use chopin_pg::{PgPool, PgPoolConfig};
use std::time::Duration;
let pool_cfg = PgPoolConfig::new()
.max_size(32)
.min_size(8)
.checkout_timeout(Duration::from_secs(3))
.connection_timeout(Duration::from_secs(3))
.idle_timeout(Duration::from_secs(300))
.max_lifetime(Duration::from_secs(1800))
.test_on_checkout(true);
let mut pool = PgPool::connect_with_config(config.clone(), pool_cfg)?;
// Non-blocking checkout
let _guard = pool.try_get()?;
println!(
"idle={} active={} total={}",
pool.idle_connections(),
pool.active_connections(),
pool.total_connections()
);
let stats = pool.stats();
println!("checkouts={} created={}", stats.total_checkouts, stats.total_connections_created);
7) Connection tuning (timeout + statement cache)
use std::time::Duration;
conn.set_io_timeout(Duration::from_secs(2));
conn.set_statement_cache_capacity(512);
// ... run workload ...
conn.clear_statement_cache();
8) Error handling and retry policy
use chopin_pg::{ErrorClass, PgError};
match conn.query_one("SELECT * FROM users WHERE id = $1", &[&id]) {
Ok(row) => { /* use row */ }
Err(err) => match err.classify() {
ErrorClass::Transient => eprintln!("retry candidate: {}", err),
ErrorClass::Pool => eprintln!("pool pressure: {}", err),
ErrorClass::Client | ErrorClass::Permanent => eprintln!("fail fast: {}", err),
},
}
Zero → Hero checklist: start with PgConnection, move to worker-local
PgPool, then add transactions, COPY, LISTEN/NOTIFY, and tuning knobs. This gives full
coverage of the public chopin-pg feature set.
ORM Reference
The chopin-orm crate maps your Rust structs directly to PostgreSQL tables with zero
runtime overhead using the Model derive macro.
#[derive(Model, Serialize, Deserialize)]
#[model(table_name = "inventories")]
pub struct Item {
#[model(primary_key)]
pub sku: String,
pub quantity: i32,
pub last_restock: Option,
}
Model Methods
| Method | Signature | Behavior |
|---|---|---|
insert |
&mut self, &mut Executor |
Creates record; populates PK if generated. |
update |
&self, &mut Executor |
Updates all fields matching the Primary Key. |
upsert |
&mut self, &mut Executor |
Insert or Update on Primary Key conflict. |
delete |
&self, &mut Executor |
Permanently removes record matching PK. |
Advanced Querying
The QueryBuilder generates optimized SQL and handles parameter binding automatically.
use chopin_orm::QueryBuilder;
fn find_low_stock(min: i32) -> Vec- {
DB.with(|db| {
let mut pool = db.borrow_mut();
QueryBuilder::
- ::new()
.filter("quantity < $1", vec![min.to_param()])
.order_by("quantity ASC")
.limit(50)
.all(&mut *pool) // Executes on the pool
.unwrap()
})
}
Atomic Transactions
Chopin transactions are synchronous and deterministic. Pass a &mut Transaction anywhere
an Executor is required.
use chopin_orm::Transaction;
DB.with(|db| {
let mut pool = db.borrow_mut();
let mut conn = pool.get().unwrap();
let mut tx = Transaction::begin(&mut conn).unwrap();
item.quantity -= 1;
item.update(&mut tx).unwrap(); // Part of transaction
log.record(&mut tx).unwrap(); // Part of transaction
tx.commit().unwrap(); // Atomic success
});
WebSocket (RFC 6455)
Chopin provides a built-in WebSocket upgrade handshake and a full RFC 6455 frame codec with no external dependencies.
Upgrade a connection
use chopin_core::websocket;
#[get("/ws")]
fn ws_handler(ctx: Context) -> Response {
// Validate Upgrade headers and build 101 Switching Protocols response
match websocket::ws_upgrade(&ctx) {
Some(resp) => resp, // 101 — caller should now use the raw fd for frames
None => Response::bad_request(),
}
}
Frame codec
use chopin_core::websocket::{decode_frame, encode_frame, WsMessage, OPCODE_TEXT};
// Decode a received frame from a byte buffer
let (frame, consumed) = decode_frame(&buf)?;
// Build an outgoing text frame
let bytes = encode_frame(OPCODE_TEXT, b"hello from server", false);
// High-level message assembly (handles fragmentation)
let msg = WsMessage::Text("hello".into());
| Feature | Status |
|---|---|
Upgrade handshake (Sec-WebSocket-Accept) | ✅ Production |
| Text / Binary frames | ✅ Production |
| Ping / Pong / Close frames | ✅ Production |
| Frame fragmentation (continuation) | ✅ Production |
| Client-side masking validation | ✅ RFC-compliant |
| Max frame payload (16 MiB) | ✅ Configurable constant |
TLS / HTTPS
Enable TLS with the tls feature flag. Chopin uses rustls
(TLS 1.2/1.3) for in-worker encryption — no separate proxy required.
Enable in Cargo.toml
[dependencies]
chopin-core = { version = "0.5.27", features = ["tls"] }
Serve with TLS
use chopin_core::{get, Context, Response, Chopin};
#[get("/")]
fn index(_ctx: Context) -> Response {
Response::text("Hello, secure world!")
}
fn main() {
Chopin::new()
.mount_all_routes()
.serve_tls("0.0.0.0:443", "certs/cert.pem", "certs/key.pem")
.unwrap();
}
Both PEM certificate chains and private keys are supported. Compatible with AWS ACM private CA bundles and self-signed certs for development.
Observability
Prometheus metrics endpoint
Mount a Prometheus-compatible scrape endpoint that aggregates counters across all worker threads.
Chopin::new()
.mount_all_routes()
.with_metrics("/metrics") // GET /metrics → Prometheus text format
.with_health("/health") // GET /health → JSON probe (k8s / ALB)
.serve("0.0.0.0:8080")
.unwrap();
| Metric | Type | Description |
|---|---|---|
chopin_requests_total | Counter | Total HTTP requests handled across all workers. |
chopin_active_connections | Gauge | Currently open connections across all workers. |
chopin_bytes_sent_total | Counter | Total response bytes sent. |
chopin_uptime_seconds | Gauge | Server uptime in seconds. |
Health endpoint
GET /health returns 200 OK with a JSON body suitable for Kubernetes
liveness/readiness probes and AWS ALB health checks:
{"status":"ok","uptime_secs":1234,"workers":8,"requests":9876543,"active_connections":42,"bytes_sent":987654321}
Structured JSON logging
Enable JSON-structured logging to stderr with the logging feature. Log level is
controlled by the RUST_LOG environment variable.
[dependencies]
chopin-core = { version = "0.5.27", features = ["logging"] }
Chopin::new()
.mount_all_routes()
.with_logging() // reads RUST_LOG; default: info
.serve("0.0.0.0:8080")
.unwrap();