Overview
Elysia provides two powerful mechanisms for managing application-level data and extending the context object:
State : Mutable global store accessible across all routes
Decorators : Custom properties added to the context object
State
State creates a mutable global store that can be accessed and modified across all route handlers.
Basic usage
import { Elysia } from 'elysia'
const app = new Elysia ()
. state ( 'counter' , 0 )
. get ( '/increment' , ({ store }) => {
store . counter ++
return store . counter
})
. get ( '/count' , ({ store }) => store . counter )
. listen ( 3000 )
Multiple state values
You can define multiple state values by chaining .state() calls:
const app = new Elysia ()
. state ( 'name' , 'Elysia' )
. state ( 'version' , '1.0.0' )
. state ( 'build' , 0 )
. get ( '/info' , ({ store }) => ({
name: store . name ,
version: store . version ,
build: store . build
}))
. listen ( 3000 )
Type-safe state
Elysia automatically infers state types:
const app = new Elysia ()
. state ( 'users' , new Map < string , { name : string ; age : number }>())
. get ( '/users/:id' , ({ store , params }) => {
const user = store . users . get ( params . id )
return user ?? { error: 'User not found' }
})
. post ( '/users/:id' , ({ store , params , body }) => {
store . users . set ( params . id , body )
return { success: true }
}, {
body: t . Object ({
name: t . String (),
age: t . Number ()
})
})
. listen ( 3000 )
State from example
Based on the source code example (example/store.ts:4-6):
import { Elysia } from 'elysia'
new Elysia ()
. state ( 'name' , 'Fubuki' )
. get ( '/id/:id' , ({ params : { id }, store : { name } }) => ` ${ id } ${ name } ` )
. listen ( 3000 )
Decorators
Decorators add custom properties to the context object, making them available in all route handlers.
Basic usage
import { Elysia } from 'elysia'
const app = new Elysia ()
. decorate ( 'getDate' , () => new Date (). toISOString ())
. get ( '/time' , ({ getDate }) => getDate ())
. listen ( 3000 )
Adding multiple decorators
const app = new Elysia ()
. decorate ( 'db' , database )
. decorate ( 'logger' , logger )
. decorate ( 'config' , config )
. get ( '/users' , async ({ db }) => {
return await db . users . findMany ()
})
. listen ( 3000 )
Function decorators
Decorators can be functions that are called within route handlers:
const app = new Elysia ()
. decorate ( 'formatResponse' , ( data : any ) => ({
success: true ,
data ,
timestamp: Date . now ()
}))
. get ( '/users' , ({ formatResponse }) =>
formatResponse ([ 'Alice' , 'Bob' ])
)
. listen ( 3000 )
Derive
Derive creates computed properties based on the context, executed on each request:
import { Elysia } from 'elysia'
const app = new Elysia ()
. state ( 'counter' , 0 )
. derive (({ store }) => ({
increase () {
store . counter ++
}
}))
. derive (({ store }) => ({
doubled: store . counter * 2 ,
tripled: store . counter * 3
}))
. get ( '/' , ({ increase , store }) => {
increase ()
const { counter , doubled , tripled } = store
return {
counter ,
doubled ,
tripled
}
})
. listen ( 3000 )
This example is from example/derive.ts:3-28 and shows how derive can:
Add methods to the context
Create computed properties from state
Chain multiple derivations
Derive with authentication
const app = new Elysia ()
. derive (({ headers }) => {
const token = headers . authorization ?. replace ( 'Bearer ' , '' )
return {
userId: decodeToken ( token )?. userId
}
})
. get ( '/profile' , ({ userId }) => {
if ( ! userId ) {
return { error: 'Unauthorized' }
}
return { userId }
})
. listen ( 3000 )
State vs Decorators vs Derive
State Mutable global store
Shared across all requests
Can be modified
Persists between requests
Decorators Immutable context additions
Added once at startup
Same instance for all requests
Good for services, config
Derive Computed per request
Executed on each request
Can access request context
Good for auth, parsing
Scoping
State, decorators, and derive can be scoped to plugins:
const plugin = new Elysia ()
. state ( 'plugin-state' , 'value' )
. decorate ( 'pluginHelper' , () => 'helper' )
const app = new Elysia ()
. use ( plugin )
. get ( '/test' , ({ store , pluginHelper }) => ({
state: store [ 'plugin-state' ],
helper: pluginHelper ()
}))
. listen ( 3000 )
Real-world example
Combining state, decorators, and derive for a complete application:
import { Elysia } from 'elysia'
interface Session {
userId : string
createdAt : number
}
const app = new Elysia ()
// Global state
. state ( 'sessions' , new Map < string , Session >())
. state ( 'requestCount' , 0 )
// Decorators for services
. decorate ( 'logger' , {
info : ( msg : string ) => console . log ( `[INFO] ${ msg } ` ),
error : ( msg : string ) => console . error ( `[ERROR] ${ msg } ` )
})
// Derive authentication info per request
. derive (({ headers , store , logger }) => {
store . requestCount ++
const sessionId = headers [ 'x-session-id' ]
const session = sessionId ? store . sessions . get ( sessionId ) : null
if ( session ) {
logger . info ( `Session found: ${ session . userId } ` )
}
return { session }
})
. get ( '/stats' , ({ store }) => ({
activeSessions: store . sessions . size ,
totalRequests: store . requestCount
}))
. get ( '/profile' , ({ session , error }) => {
if ( ! session ) {
return error ( 401 , 'No active session' )
}
return { userId: session . userId }
})
. listen ( 3000 )
State is mutable and shared across all requests. Be careful with concurrent access and consider using proper locking mechanisms for critical operations.
Best practices
Store only what you need globally. Consider using databases or external state management for larger datasets.
Use decorators for services
Database connections, loggers, and configuration are perfect candidates for decorators.
Derive for per-request data
Use derive for authentication, request parsing, and other per-request computations.
Let TypeScript infer your types automatically for better type safety and autocomplete.