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 GitHubWhy 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
bun add elysia-scoped-stateGetting Started
Initialize the plugin with your state schema. Each key defines a piece of state with an initial value:
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:
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:
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:
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:
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:
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:
- On first request, a secure
user_session_idcookie is created - Each subsequent request uses this cookie to retrieve the user's state
- 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_idcookie 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