Skip to content

Session data

The Session type is the main interface to work with sessions in Pavex.
You can think of the session state as a collection of key-value pairs. The keys are strings, the values are arbitrary—e.g. strings, numbers, or even complex data structures1.

Let's go through a few examples to get familiar with the basic operations you can perform.

Storing data

Use insert to store an entry in the server-side state of your session:

src/ops/insert.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

pub async fn handler(session: &mut Session<'_> ) -> Result<Response, Error> {
    session.insert("user.id", "my-user-identifier").await?;
    // [...]

In the example above, insert will create a new user.id entry in the session state. If there is already a user.id entry, it'll overwrite it.

Complex objects

You're not limited to storing simple values. You can store complex types like structs or enums as well, if they are serializable:

src/ops/insert_struct.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

#[derive(serde::Serialize, serde::Deserialize)] 
struct AuthInfo {
    user_id: String,
    email: String,
}

pub async fn handler(session: &mut Session<'_>) -> Result<Response, Error> {
    let info = AuthInfo {
        user_id: "my-user-identifier".into(),
        email: "user@domain.com".into(),
    };
    session.insert("user", info).await?; 
    // [...]
}

Retrieving data

Use get to retrieve an entry from the server-side state of your session:

src/ops/get.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

pub async fn handler(session: &Session<'_> ) -> Result<Response, Error> {
    let user_id: Option<String>  = session.get("user.id").await?; 
    // [...]
}

Complex objects

The process is exactly the same for more complex types. You just need to specify the type you expect to get back:

src/ops/get_struct.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

#[derive(serde::Serialize, serde::Deserialize)] 
struct AuthInfo {
    user_id: String,
    email: String,
}

pub async fn handler(session: &Session<'_>) -> Result<Response, Error> {
    let auth_info: Option<AuthInfo>  = session.get("user").await?;
    // [...]
}

Removing data

Use remove to delete an entry from the server-side state of your session:

src/ops/remove.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

pub async fn handler(session: &mut Session<'_>) -> Result<Response, Error> {
    let user_id: Option<String>  = session.remove("user.id").await?;
    // [...]
}

Remove returns the entry that was removed, if it existed, or None otherwise.
If you don't plan to use the removed value, you can invoke remove_raw instead:

src/ops/remove_raw.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

pub async fn handler(session: &mut Session<'_>) -> Result<Response, Error> {
    session.remove_raw("user.id").await?;
    // [...]
}

It returns the raw entry, without trying to deserialize it. It spares you from having to specify the type.

Regenerating the session ID

Your application may be required to regenerate the session ID to prevent session fixation attacks.
You can do this by calling cycle_id:

src/ops/cycle_id.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

pub async fn handler(session: &mut Session<'_>) -> Result<Response, Error> {
    session.cycle_id();
    // [...]
}

cycle_id doesn't change the session state in any way.

Session invalidation

If you want to destroy the current session, call invalidate:

src/ops/invalidate.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

pub async fn handler(session: &mut Session<'_>) -> Result<Response, Error> {
    session.invalidate();
    // [...]
}

invalidate will:

  • remove the server-side state from the storage backend
  • delete the session cookie on the client-side

It effectively ends the current session.
All operations on the current session after invoking invalidate will be ignored.

Deletion without invalidation

There may be situations where you want to keep the session alive, but remove all data from the server-side state. Use clear:

src/ops/clear.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

pub async fn handler(session: &mut Session<'_>) -> Result<Response, Error> {
    session.clear().await?;
    // [...]
}

clear removes all entries from the server-side state, leaving an empty record in the storage backend.
If you want to delete the server-side state entry completely, use delete:

src/ops/delete.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

pub async fn handler(session: &mut Session<'_>) -> Result<Response, Error> {
    session.delete();
    // [...]
}

delete will remove the server-side state entry from the storage backend, but it won't delete the session cookie on the client-side.

Client-side state

As we discussed in the introduction, there are two types of session data: the client-side state and the server-side state.
All the examples above manipulate the server-side state, but there may be cases where you want to store data in the client-side state to minimize the number of round-trips to the session storage backend.

You can use client and client_mut to perform the same operations on the client-side state:

src/ops/client.rs
use anyhow::Error;
use pavex::response::Response;
use pavex_session::Session;

pub async fn handler(session: &mut Session<'_>) -> Result<Response, Error> {
    let key = "user.id";
    let value = "my-user-identifier";

    // Insertion
    session.client_mut().insert(key, value)?;

    // Retrieval
    let stored: Option<String> = session.client().get(key)?;
    assert_eq!(stored.as_deref(), Some(value));

    // Removal
    session.client_mut().remove_raw(key);
    assert_eq!(session.client().get_raw(key), None);
    // [...]
}

Keep in mind that the client-side state is stored inside the session cookie.
It's not suitable for storing large amounts of data and it is inherently more exposed than its server-side counterpart. Use it only for small, non-sensitive data.


  1. Internally, each value is stored as a JSON object. This means that you can store any type that can be serialized to (and deserialized from) JSON. In Rust terms, you can reason about the session state as if it were a HashMap<String, serde_json::Value>