Build Status Crates.io Downloads
Chopin Logo

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 itoa formatting removes std::fmt overhead.

Native Asynchronous Core

Platform native asynchronous I/O with direct interaction with the underlying OS kernels for maximum efficiency.

  • Platform Native: Direct kqueue (macOS) and epoll (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")
}
ExtensionContent-TypeExtensionContent-Type
.htmltext/html.jsonapplication/json
.jsapplication/javascript.csstext/css
.pngimage/png.jpg/.jpegimage/jpeg
.svgimage/svg+xml.webpimage/webp
.wasmapplication/wasm.gzapplication/gzip
.pdfapplication/pdf.mp4video/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.

MethodStatusBody / Behaviour
Response::text(body)200Allocates a byte body with text/plain content-type.
Response::text_static(body)200Zero-alloc. Takes &'static [u8]. Ideal for fixed messages.
Response::json(&val)200Serializes T: Serialize via Schema-JIT engine.
Response::json_bytes(body)200Pre-serialized JSON bytes. Sets application/json.
Response::json_static(body)200Zero-alloc. Takes &'static [u8] for pre-serialized JSON known at compile time.
Response::raw(bytes)200Ultra-fast. Writes a complete pre-baked HTTP/1.1 response verbatim — bypasses all header serialization.
Response::stream(iter)200Chunked streaming body from an iterator of Vec<u8>.
Response::file(path)200/404Zero-copy sendfile. Auto Content-Type. Returns 404 on missing file.
Response::file_range(path, range)200/206/416Serves file with RFC 7233 Range support. Pass ctx.header("range").
Response::sendfile(fd, offset, len, ct)200Custom range sendfile with explicit content-type.
Response::new(status)anyEmpty body with given status code.
Response::not_found()404Plain-text "Not Found".
Response::bad_request()400Plain-text "Bad Request".
Response::unauthorized()401Plain-text "Unauthorized".
Response::forbidden()403Plain-text "Forbidden".
Response::server_error()500Plain-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());
FeatureStatus
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();
MetricTypeDescription
chopin_requests_totalCounterTotal HTTP requests handled across all workers.
chopin_active_connectionsGaugeCurrently open connections across all workers.
chopin_bytes_sent_totalCounterTotal response bytes sent.
chopin_uptime_secondsGaugeServer 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();