User Sessions
User sessions are crucial for managing authentication, security, auditing, and user-specific state in modern applications. Pikku provides a unified session abstraction through the userSessionService that works consistently across different protocols.
Sessions are designed for interactive, user-facing protocols:
- HTTP β Cookie or token-based sessions
- WebSocket (Channels) β Connection-based sessions
- CLI β Interactive command sessions
- MCP β AI agent sessions
Queue workers and scheduled tasks typically run as service accounts rather than user sessions. Pikku supports this pattern, but it's handled differently and will be enhanced in future versions.
How It Worksβ
Pikku doesn't make assumptions about how sessions are stored or managed. Instead, it provides the userSessionService abstraction that your middleware uses to load and persist sessions.
The key benefit: your functions don't need to know if the session comes from an HTTP cookie, a WebSocket connection, or somewhere else. They just use the session parameter, and it works across all protocols.
Session Lifecycleβ
-
Middleware loads the session β Pikku's session middleware uses the
userSessionServiceto load session data based on the protocol (e.g., from a cookie for HTTP, from connection state for WebSocket) -
Function receives session β Your function gets the session as the third parameter
-
Function can modify session β Use
userSession.set()oruserSession.clear()to update session state -
Middleware persists changes β Pikku's middleware saves any session changes back to the store
This separation means you can use different session strategies per protocol while keeping your function code identical.
Pikku Session Middlewareβ
Pikku provides session middleware for different protocols:
@pikku/middleware-http-sessionβ Cookie-based sessions for HTTP@pikku/middleware-channel-sessionβ Connection-based sessions for WebSocket channels
These middleware handle loading sessions before your functions execute and persisting any changes afterward. You can customize the behavior or implement your own session middleware.
Defining Your Session Typeβ
Define what data you want to store in sessions by creating your UserSession type:
// types/application-types.d.ts
import { CoreUserSession } from '@pikku/core'
interface UserSession extends CoreUserSession {
userId: string
email: string
role: 'user' | 'admin'
permissions: string[]
}
Now your session type is available throughout your application with full type safety.
Setting a Session (Login)β
Use userSession.set() to create or update a session. This typically happens in a login function:
import { pikkuSessionlessFunc, UnauthorizedError } from '@pikku/core'
type LoginInput = {
email: string
password: string
}
type LoginResult = {
token: string
user: { id: string; email: string; role: string }
}
export const login = pikkuSessionlessFunc<LoginInput, LoginResult>({
func: async ({ database, userSession, jwt }, data) => {
// Verify credentials
const user = await database.query('user', { email: data.email })
if (!user || !await verifyPassword(data.password, user.passwordHash)) {
throw new UnauthorizedError('Invalid credentials')
}
// Set the session - middleware will persist it
await userSession.set({
userId: user.id,
email: user.email,
role: user.role,
permissions: user.permissions
})
// Optionally return a JWT for client-side storage
// (useful for mobile apps or when cookies aren't available)
return {
token: await jwt.sign({ userId: user.id }),
user: {
id: user.id,
email: user.email,
role: user.role
}
}
},
auth: false, // No existing session required for login
docs: {
summary: 'Authenticate a user',
tags: ['auth']
}
})
Notice we used pikkuSessionlessFunc and set auth: false β this function doesn't require an existing session since we're creating one.
Clearing a Session (Logout)β
Use userSession.clear() to end a session:
export const logout = pikkuFunc<void, { success: boolean }>({
func: async ({ userSession }) => {
await userSession.clear()
return { success: true }
},
docs: {
summary: 'Logout user',
tags: ['auth']
}
})
What happens after userSession.clear() depends on the protocol:
- HTTP: Session cookie is cleared
- WebSocket: Connection's session is marked as cleared (connection stays open)
- CLI: Authenticated session ends
Accessing Session Dataβ
In any function with auth: true (the default), the session is available as the third parameter:
export const getProfile = pikkuFunc<void, UserProfile>({
func: async ({ database }, data, session) => {
// session is guaranteed to exist because auth: true
return await database.query('userProfile', {
userId: session.userId
})
},
docs: {
summary: 'Get current user profile',
tags: ['user']
}
})
For functions that work with or without authentication, use pikkuSessionlessFunc:
export const getPublicData = pikkuSessionlessFunc<void, PublicData>({
func: async ({ database }, data, session) => {
// session might be undefined
if (session) {
// User is logged in, return personalized data
return await database.query('publicData', { userId: session.userId })
}
// Return generic data for anonymous users
return await database.query('publicData', { isPublic: true })
},
auth: false,
docs: {
summary: 'Get public data (personalized if logged in)',
tags: ['public']
}
})
Updating Session Dataβ
You can update the session anytime during a function call:
export const updatePreferences = pikkuFunc<PreferencesInput, void>({
func: async ({ database, userSession }, data, session) => {
// Save to database
await database.update('userPreferences', {
where: { userId: session.userId },
set: data
})
// Update session - middleware will persist the change
await userSession.set({
...session,
preferences: data,
updatedAt: new Date().toISOString()
})
},
docs: {
summary: 'Update user preferences',
tags: ['user']
}
})
Custom Middlewareβ
You can create custom middleware to extend session behavior:
// Automatically update last activity timestamp
export const refreshSessionMiddleware = pikkuMiddleware(async (
{ userSession },
interaction,
next
) => {
if (session) {
await userSession.set({
...session,
lastActivity: new Date().toISOString()
})
}
return await next()
})
Summaryβ
Pikku's session system provides:
- Transport-agnostic abstraction β Same
userSessionServiceAPI for all protocols - Middleware-driven β Pikku's middleware handles loading and persisting sessions
- Type-safe β Full TypeScript support for your session data
- Protocol support β Works with HTTP, WebSocket, CLI, and MCP
- Flexible β Use cookies, tokens, connection state, or custom strategies
The userSessionService is the key: it abstracts session management so your functions work identically whether called via HTTP, WebSocket, or any other protocol that supports user sessions.
For more details, see: