AbsoluteJS
AbsoluteJS

Elysia Scoped State

An Elysia plugin for per-user session state management. Store and retrieve data tied to individual users across requests with automatic session handling.

View on GitHub
#

Why Scoped State?

Per-User Isolation

Each user gets their own state slice. Button clicks and interactions only affect that user's data.

Automatic Sessions

A secure session cookie is automatically created on first visit. No manual session management required.

HTMX Perfect

Ideal for HTMX apps where server endpoints need to maintain user-specific state across partial page updates.

Type Safe

Full TypeScript support with typed state access through the scopedStore context property.

#

Installation

BASH
bun add elysia-scoped-state
#

Getting Started

Initialize the plugin with your state schema. Each key defines a piece of state with an initial value:

TS
1import Elysia from 'elysia';
2import { scopedState } from 'elysia-scoped-state';
3
4const app = new Elysia()
5  .use(
6    scopedState({
7      count: { value: 0 },
8      username: { value: '', preserve: true }
9    })
10  )
11  .listen(3000);
#

Accessing State

Access your scoped state through the scopedStore context property. Each user sees and modifies only their own state:

TS
1// Access and modify scoped state in route handlers
2new Elysia()
3  .use(scopedState({ count: { value: 0 } }))
4  .get('/api/count', ({ scopedStore }) => {
5    // Each user sees their own count
6    return scopedStore.count;
7  })
8  .post('/api/increment', ({ scopedStore }) => {
9    // Increment only affects this user's state
10    return ++scopedStore.count;
11  })
12  .post('/api/decrement', ({ scopedStore }) => {
13    return --scopedStore.count;
14  });
#

Preserve Option

Mark state as preserve: true to keep it across page refreshes and navigation. Without this, state resets when users refresh the page or navigate away. Useful for user preferences and session data that should persist:

TS
1// The preserve option keeps state across page refreshes and navigation
2scopedState({
3  // This resets when the user refreshes or navigates away
4  count: { value: 0 },
5
6  // This persists across page refreshes and switches
7  theme: { value: 'light', preserve: true },
8  username: { value: '', preserve: true }
9})
#

Resetting State

Use resetScopedStore() to programmatically reset the user's state to initial values. This respects preserve flags by default. Pass true to ignore preserve flags and reset everything:

TS
1// Programmatically reset the user's scoped store
2new Elysia()
3  .use(scopedState({
4    count: { value: 0 },
5    theme: { value: 'light', preserve: true }
6  }))
7  .post('/api/reset', ({ resetScopedStore }) => {
8    // Resets count to 0, but keeps theme (respects preserve)
9    resetScopedStore();
10    return 'State reset!';
11  })
12  .post('/api/full-reset', ({ resetScopedStore }) => {
13    // Ignores preserve flags, resets everything including theme
14    resetScopedStore(true);
15    return 'Full state reset!';
16  });
#

HTMX Integration

Scoped state shines with HTMX. Each user's button clicks and interactions only affect their own count, cart, or other state:

TS
1// Complete HTMX counter example with scoped state
2import Elysia from 'elysia';
3import { scopedState } from 'elysia-scoped-state';
4import { handleHTMXPageRequest } from '@absolutejs/absolute';
5
6new Elysia()
7  .use(
8    scopedState({
9      count: { value: 0 }
10    })
11  )
12  .get('/app', () => handleHTMXPageRequest('./build/pages/counter.html'))
13  .get('/api/count', ({ scopedStore }) => {
14    return `<span id="count">${scopedStore.count}</span>`;
15  })
16  .post('/api/increment', ({ scopedStore }) => {
17    return `<span id="count">${++scopedStore.count}</span>`;
18  })
19  .post('/api/decrement', ({ scopedStore }) => {
20    return `<span id="count">${--scopedStore.count}</span>`;
21  })
22  .listen(3000);

The HTML uses HTMX attributes to call these endpoints:

HTML
1<!-- counter.html -->
2<!DOCTYPE html>
3<html lang="en">
4<head>
5  <title>Counter App</title>
6  <script src="https://unpkg.com/[email protected]"></script>
7</head>
8<body>
9  <h1>Personal Counter</h1>
10
11  <div>
12    Count: <span id="count" hx-get="/api/count" hx-trigger="load">0</span>
13  </div>
14
15  <button hx-post="/api/increment" hx-target="#count" hx-swap="outerHTML">
16    +1
17  </button>
18
19  <button hx-post="/api/decrement" hx-target="#count" hx-swap="outerHTML">
20    -1
21  </button>
22</body>
23</html>
  • User A clicks increment 3 times → sees count of 3
  • User B visits the same page → sees count of 0 (their own state)
  • User B clicks increment → sees count of 1 (independent from User A)
#

How It Works

The plugin uses a secure session cookie to identify users and maintain their state server-side:

  1. On first request, a secure user_session_id cookie is created
  2. Each subsequent request uses this cookie to retrieve the user's state
  3. State is stored server-side, keyed by session ID

User A

Visits /api/count → sees 0
Calls /api/increment → sees 1
Calls /api/increment → sees 2

User B

Visits /api/count → sees 0 (own state)
Calls /api/increment → sees 1
Independent from User A

  • Automatic Session ID: A user_session_id cookie is created on first request
  • Server-Side Storage: State is stored in memory on the server, keyed by session ID
  • Isolation: Each session ID maps to a completely separate state object