| Crates.io | salvo-express-session |
| lib.rs | salvo-express-session |
| version | 0.2.0 |
| created_at | 2025-12-31 12:47:18.657803+00 |
| updated_at | 2025-12-31 13:04:10.715042+00 |
| description | Express-session compatible session middleware for Salvo, with connect-redis support |
| homepage | |
| repository | https://github.com/yshing/salvo-express-session |
| max_upload_size | |
| id | 2014633 |
| size | 234,227 |
Express-session compatible session middleware for Salvo web framework.
This crate provides session management that is fully compatible with Node.js express-session and connect-redis, allowing seamless session sharing between Rust and Node.js applications.
s: prefix and HMAC-SHA256 cookie signature formatAdd to your Cargo.toml:
[dependencies]
salvo-express-session = "0.1"
salvo = { version = "0.87", features = ["cookie"] }
tokio = { version = "1", features = ["full"] }
For Redis support:
[dependencies]
salvo-express-session = { version = "0.1", features = ["redis-store"] }
use salvo::prelude::*;
use salvo_express_session::{ExpressSessionHandler, MemoryStore, SessionConfig, SessionDepotExt};
#[handler]
async fn index(depot: &mut Depot) -> String {
let session = depot.session_mut().expect("Session not found");
let views: i32 = session.get("views").unwrap_or(0);
session.set("views", views + 1);
format!("Views: {}", views + 1)
}
#[tokio::main]
async fn main() {
let store = MemoryStore::new();
let config = SessionConfig::new("your-secret-key")
.with_cookie_name("connect.sid")
.with_max_age(86400); // 1 day
let session_handler = ExpressSessionHandler::new(store, config);
let router = Router::new()
.hoop(session_handler)
.get(index);
let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
Server::new(acceptor).serve(router).await;
}
use salvo::prelude::*;
use salvo_express_session::{ExpressSessionHandler, RedisStore, SessionConfig, SessionDepotExt};
#[tokio::main]
async fn main() {
let store = RedisStore::from_url("redis://127.0.0.1/")
.await
.expect("Failed to connect to Redis");
// Use same secret as your Node.js app for session sharing!
let config = SessionConfig::new("keyboard cat")
.with_cookie_name("connect.sid")
.with_prefix("sess:")
.with_max_age(86400);
let session_handler = ExpressSessionHandler::new(store, config);
// ... rest of your app
}
use chrono::{Utc, Duration};
#[handler]
async fn example(depot: &mut Depot) {
let session = depot.session_mut().expect("Session not found");
// Get session ID
let id = session.id();
// Get/set values
let user: Option<String> = session.get("user");
session.set("user", "alice");
// Remove a value
session.remove("user");
// Check if key exists
if session.contains("user") {
// ...
}
// Clear all session data
session.clear();
// Destroy session (removes from store and clears cookie)
session.destroy();
// Regenerate session ID (security best practice after login)
session.regenerate();
// Check session status
let is_new = session.is_new();
let is_modified = session.is_modified();
// Dynamic cookie expiration (like express-session)
// Set expiration to 1 hour from now
session.set_cookie_expires(Some(Utc::now() + Duration::hours(1)));
// Or set max age in seconds
session.set_cookie_max_age_secs(3600); // 1 hour
// Or set max age in milliseconds (like express-session)
session.set_cookie_max_age(Some(60 * 60 * 1000)); // 1 hour
}
let config = SessionConfig::new("secret")
// Cookie name (default: "connect.sid")
.with_cookie_name("connect.sid")
// Cookie path (default: "/")
.with_cookie_path("/")
// Cookie domain (default: None - current domain only)
.with_cookie_domain("example.com")
// HttpOnly flag (default: true)
.with_http_only(true)
// Secure flag - requires HTTPS (default: false)
.with_secure(true)
// SameSite attribute (default: Lax)
.with_same_site(SameSite::Strict)
// Max age in seconds (default: None = session cookie)
// Session cookies expire when browser closes
.with_max_age(3600) // 1 hour
// Or for session cookie (expires on browser close):
// .with_max_age(None)
// Session key prefix in store (default: "sess:")
.with_prefix("sess:")
// Save uninitialized sessions (default: false)
.with_save_uninitialized(false)
// Force save on every request (default: false)
.with_resave(false)
// Reset cookie expiry on every request (default: false)
.with_rolling(true);
For zero-downtime secret rotation:
// New secret first, old secrets after
let config = SessionConfig::with_secrets(vec![
"new-secret",
"old-secret",
"older-secret",
]);
New sessions are signed with the first secret. Existing sessions signed with any secret in the list are accepted.
To share sessions between Rust and Node.js:
let config = SessionConfig::new("keyboard cat")
.with_cookie_name("connect.sid")
.with_prefix("sess:");
const session = require('express-session');
const RedisStore = require('connect-redis').default;
app.use(session({
store: new RedisStore({ client: redisClient, prefix: 'sess:' }),
secret: 'keyboard cat',
name: 'connect.sid',
resave: false,
saveUninitialized: false,
}));
With matching configuration, sessions are fully interchangeable!
Sessions are stored as JSON with this structure (compatible with express-session):
{
"cookie": {
"originalMaxAge": 86400000,
"expires": "2024-12-31T23:59:59.000Z",
"secure": false,
"httpOnly": true,
"path": "/"
},
"user": "alice",
"views": 42
}
Implement the SessionStore trait for custom backends:
use async_trait::async_trait;
use salvo_express_session::{SessionStore, SessionData, SessionError};
struct MyStore;
#[async_trait]
impl SessionStore for MyStore {
async fn get(&self, sid: &str) -> Result<Option<SessionData>, SessionError> {
// ...
}
async fn set(&self, sid: &str, session: &SessionData, ttl: Option<u64>) -> Result<(), SessionError> {
// ...
}
async fn destroy(&self, sid: &str) -> Result<(), SessionError> {
// ...
}
async fn touch(&self, sid: &str, session: &SessionData, ttl: Option<u64>) -> Result<(), SessionError> {
// ...
}
}
Run the basic example:
cargo run --example basic
Run the Redis example:
# Start Redis first
docker run -p 6379:6379 redis
# Run the example
cargo run --example with_redis --features redis-store
Test Node.js compatibility:
# Terminal 1: Start Rust app
cargo run --example with_redis --features redis-store
# Terminal 2: Start Node.js app
cd examples/node-compatibility
npm install
npm start
# Both apps share sessions via Redis!
MIT OR Apache-2.0