{"id":35180961,"url":"https://github.com/jimmielovell/ruts","last_synced_at":"2026-04-02T02:18:42.816Z","repository":{"id":218783980,"uuid":"743738217","full_name":"jimmielovell/ruts","owner":"jimmielovell","description":"A robust, flexible session management library for Rust web applications. It provides a seamless way to handle cookie sessions in tower-based web frameworks, with a focus on security, performance, and ease of use.","archived":false,"fork":false,"pushed_at":"2026-03-05T22:40:42.000Z","size":253,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-06T02:05:46.447Z","etag":null,"topics":["axum","redis-cache","sessions","tower-middleware"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jimmielovell.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-01-15T22:00:11.000Z","updated_at":"2026-03-05T22:32:11.000Z","dependencies_parsed_at":"2024-02-18T16:25:20.569Z","dependency_job_id":"5a4f92dd-9020-483e-bf44-d391fb23cd87","html_url":"https://github.com/jimmielovell/ruts","commit_stats":null,"previous_names":["jimmielovell/ruse","jimmielovell/ruts"],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/jimmielovell/ruts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jimmielovell%2Fruts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jimmielovell%2Fruts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jimmielovell%2Fruts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jimmielovell%2Fruts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jimmielovell","download_url":"https://codeload.github.com/jimmielovell/ruts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jimmielovell%2Fruts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31294527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T01:43:37.129Z","status":"online","status_checked_at":"2026-04-02T02:00:08.535Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["axum","redis-cache","sessions","tower-middleware"],"created_at":"2025-12-29T01:49:32.485Z","updated_at":"2026-04-02T02:18:42.809Z","avatar_url":"https://github.com/jimmielovell.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ruts: Rust Tower Session for HTTP Applications\n\n[![Documentation](https://docs.rs/ruts/badge.svg)](https://docs.rs/ruts)\n[![Crates.io](https://img.shields.io/crates/v/ruts.svg)](https://crates.io/crates/ruts)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Rust](https://img.shields.io/badge/rust-1.85.0%2B-blue.svg?maxAge=3600)](\nhttps://github.com/jimmielovell/ruts)\n\n## Quick Start\n\nHere's a basic example with [Axum](https://docs.rs/axum/latest/axum/) and the `RedisStore`. This requires the features `axum`(enabled by default) and `redis-store` to be enabled.\n\n```rust\nuse axum::{Router, routing::get};\nuse ruts::{Session, SessionLayer, CookieOptions};\nuse ruts::store::redis::RedisStore;\nuse fred::clients::Client;\nuse std::sync::Arc;\nuse fred::interfaces::ClientLike;\nuse tower_cookies::CookieManagerLayer;\n\n#[tokio::main]\nasync fn main() {\n    // Set up Redis client\n    let client = Client::default();\n    client.connect();\n    client.wait_for_connect().await.unwrap();\n\n    // Create session store\n    let store = RedisStore::new(Arc::new(client));\n\n    // Configure session-cookie options\n    let cookie_options = CookieOptions::build()\n        .name(\"session\")\n        .http_only(true)\n        .same_site(cookie::SameSite::Lax)\n        .secure(true)\n        .max_age(3600) // 1 hour\n        .path(\"/\");\n\n    // Create session layer\n    let session_layer = SessionLayer::new(Arc::new(store))\n        .with_cookie_options(cookie_options);\n\n    // Set up router with session management\n    let app = Router::new()\n        .route(\"/\", get(handler))\n        .layer(session_layer)\n        .layer(CookieManagerLayer::new()); // CookieManagerLayer must be after\n\n    // Run the server\n    let listener = tokio::net::TcpListener::bind(\"0.0.0.0:3000\").await.unwrap();\n    axum::serve(listener, app).await.unwrap();\n}\n\nasync fn handler(session: Session\u003cRedisStore\u003cClient\u003e\u003e) -\u003e String {\n    let count: Option\u003ci32\u003e = session.get(\"count\").await.unwrap();\n    let new_count = count.unwrap_or(0) + 1;\n    session.insert(\"count\", \u0026new_count, None).await.unwrap();\n    format!(\"You've visited this page {} times\", new_count)\n}\n```\n\n## Session Management\n\n### Basic Operations\n\n```rust\nuse ruts::Session;\nuse ruts::store::SessionMap;\nuse ruts::store::memory::MemoryStore;\n\n[derive(serde::Deserialize)]\nstruct User;\n\nasync fn handler(session: Session\u003cMemoryStore\u003e) {\n  // Get a single field's data\n  let value: Option\u003cUser\u003e = session.get(\"key\").await.unwrap();\n\n  // Get all session data as a map for lazy deserialization\n  let session_map: Option\u003cSessionMap\u003e = session.get_all().await.unwrap();\n  if let Some(map) = session_map {\n      let user: Option\u003cUser\u003e = map.get(\"user\").unwrap();\n  }\n\n  // Set data (upsert) with an optional field-level expiration (in seconds)\n  // If the field exists, it is overwritten.\n  session.set(\"key\", \u0026\"some_value\", Some(3600), None).await.unwrap();\n  \n  // Prepare a new session ID before a set operation to prevent session fixation\n  let new_id = session.prepare_regenerate();\n  // The next set operation will automatically rename the session to the new ID\n  session.set(\"key\", \u0026\"value_with_new_id\", None, None).await.unwrap();\n  \n  // Remove a single field\n  session.remove(\"key\").await.unwrap();\n  \n  // Delete the entire session\n  session.delete().await.unwrap();\n  \n  // Regenerate session ID for security (Renames session immediately)\n  session.regenerate().await.unwrap();\n  \n  // Update the session's overall expiry time\n  session.expire(7200).await.unwrap();\n  \n  // Get the current session ID\n  let id = session.id();\n}\n```\n\n## Stores\n\n### Redis\nA Redis-backed session store.\n\n#### Requirements\n\n- The `redis-store` feature.\n- Redis 7.4 or later (required for field-level expiration using [HEXPIRE](https://redis.io/docs/latest/commands/hexpire/))\n- For Redis \u003c 7.4, field-level expiration will not be available\n\n```rust\nuse ruts::store::redis::RedisStore;\n\nlet store = RedisStore::new(Arc::new(fred_client_or_pool));\n```\n\n### Postgres\nA Postgres-backed session store implementation.\n\n#### Requirements\n\n- The `postgres-store` feature.\n\n```rust\nuse ruts::store::postgres::PostgresStoreBuilder;\n\n// 1. Set up your database connection pool.\nlet database_url = std::env::var(\"DATABASE_URL\")\n    .expect(\"DATABASE_URL must be set\");\nlet pool = PgPool::connect(\u0026database_url).await.unwrap();\n\n// 2. Create the session store using the builder.\n// This will also run a migration to create the `sessions` table.\nlet store = PostgresStoreBuilder::new(pool, true)\n    // Optionally, you can customize the schema and table name\n    // .schema_name(\"my_app\")\n    // .table_name(\"user_sessions\")\n    .build()\n    .await\n    .unwrap();\n```\n\n### LayeredStore\n\nA composite store that layers a fast, ephemeral \"hot\" cache (like Redis) on top of a slower, persistent \"cold\" store (like Postgres). It is designed for scenarios where sessions can have long lifespans but should only occupy expensive cache memory when actively being used thus balancing performance and durability.\n\n```rust\nuse ruts::store::layered::LayeredWriteStrategy;\nuse ruts::Session;\nuse ruts::store::redis::RedisStore;\nuse ruts::store::postgres::PostgresStore;\nuse ruts::store::layered::LayeredStore;\n\ntype MySession = Session\u003cLayeredStore\u003cRedisStore, PostgresStore\u003e\u003e;\n\n#[derive(serde::Serialize)]\nstruct User { id: i32 }\n\nasync fn handler(session: MySession) {\n    let user = User { id: 1 };\n    \n    // This session field is valid for 1 month in the persistent store.\n    let long_term_expiry = 60  * 60 * 24 * 30;\n    \n    // However, we only want it to live in the hot cache (Redis) for 1 hour.\n    let short_term_hot_cache_expiry = 60 * 60;\n    \n    // The cold store (Postgres) will get the long-term expiry,\n    // but the hot store (Redis) will be capped at the shorter TTL.\n    session.update(\"user\", \u0026user, Some(long_term_expiry), Some(short_term_hot_cache_expiry)).await.unwrap();\n}\n```\n\n## Serialization\nRuts supports two serialization backends for session data storage:\n\n- [`bincode`](https://crates.io/crates/bincode) (default) - Fast, compact binary serialization.\n- [`messagepack`](https://crates.io/crates/rmp-serde) - Cross-language compatible serialization\n\nTo use [`MessagePack`](https://crates.io/crates/rmp-serde) instead of the default [`bincode`](https://crates.io/crates/bincode), add this to your `Cargo.toml`:\n\n```toml\n[dependencies]\nruts = { version = \"0.9.0\", default-features = false, features = [\"axum\", \"messagepack\"] }\n```\n\n## Cookie Configuration\n\n```rust\nlet cookie_options = CookieOptions::build()\n    .name(\"cookie_name\")\n    .http_only(true)\n    .same_site(cookie::SameSite::Strict)\n    .secure(true)\n    .max_age(7200) // 2 hours\n    .path(\"/\")\n    .domain(\"example.com\");\n```\n\n### Signed Cookies\n\nRuts supports cryptographically signed cookies to prevent client-side tampering of the session ID. To use this, you must enable the `signed` feature in your `Cargo.toml`:\n\n```toml\n[dependencies]\nruts = { version = \"0.9.0\", features = [\"signed\"] }\n```\n\nThen you can provide a `ruts::Key` (A re-export of `tower_cookies::Key` to your CookieOptions.\n\n```rust\nlet key = Key::generate();\nlet cookie_options = CookieOptions::build()\n    .name(\"secure_session\")\n    .http_only(true)\n    .same_site(cookie::SameSite::Lax)\n    .secure(true)\n    .max_age(3600)\n    .path(\"/\")\n    .signing_key(key);\n```\n\n## Important Notes\n\n### Middleware Ordering\nThe `SessionLayer` must be applied **before** the `CookieManagerLayer`:\n\n```rust\napp.layer(session_layer)              // First: SessionLayer\n   .layer(CookieManagerLayer::new()); // Then CookieManagerLayer\n```\n\n### Best Practices\n\n- Enable HTTPS in production (set `secure: true` in cookie options)\n- Use appropriate `SameSite` cookie settings\n- Add session expiration\n- Regularly regenerate session IDs\n- Enable HTTP Only mode in production (set `http_only: true`)\n\n## Contributing\n\nContributions are welcome! Please feel free to submit issues and pull requests.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjimmielovell%2Fruts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjimmielovell%2Fruts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjimmielovell%2Fruts/lists"}