The @vk-io/session package provides a simple and flexible way to manage user sessions in your VK bot. It allows you to store and retrieve user-specific data across multiple messages and conversations.
Installation
npm install @vk-io/session
Node.js 12.20.0 or newer is required.
Quick Start
The session manager automatically attaches a session object to your message context:
import { VK } from 'vk-io' ;
import { SessionManager } from '@vk-io/session' ;
const vk = new VK ({ token: process . env . TOKEN });
const sessionManager = new SessionManager ();
vk . updates . on ( 'message_new' , sessionManager . middleware );
vk . updates . on ( 'message_new' , async ( context ) => {
if ( context . text === '/counter' ) {
// Access session data
if ( ! context . session . counter ) {
context . session . counter = 0 ;
}
context . session . counter += 1 ;
await context . send ( `You've used this command ${ context . session . counter } times` );
}
});
vk . updates . start ();
How It Works
The session manager:
Intercepts incoming messages through middleware
Loads session data for the user from storage
Attaches the session to the context object
Automatically saves changes when the handler completes
By default, sessions are stored in memory and will be lost when your bot restarts. Use custom storage for persistence.
Configuration
Create a session manager with custom options:
import { SessionManager , MemoryStorage } from '@vk-io/session' ;
const sessionManager = new SessionManager ({
storage: new MemoryStorage (),
contextKey: 'session' ,
getStorageKey : ( context ) => String ( context . senderId )
});
Options
storage
ISessionStorage
default: "MemoryStorage"
Storage adapter for persisting session data
contextKey
string
default: "'session'"
Property name for accessing session in context (e.g., context.session)
Function to generate storage key from context. Defaults to context.senderId
Storage Options
Memory Storage (Default)
Stores sessions in memory. Data is lost when the process restarts:
import { SessionManager , MemoryStorage } from '@vk-io/session' ;
const sessionManager = new SessionManager ({
storage: new MemoryStorage ()
});
Custom Storage
Implement your own storage by following the ISessionStorage interface:
interface ISessionStorage {
get ( key : string ) : Promise < object | undefined >;
set ( key : string , value : object ) : Promise < boolean >;
delete ( key : string ) : Promise < boolean >;
touch ( key : string ) : Promise < void >;
}
Redis Storage Example
import Redis from 'ioredis' ;
class RedisStorage {
private redis : Redis ;
private ttl : number ;
constructor ( options = {}) {
this . redis = new Redis ( options . redis );
this . ttl = options . ttl || 3600 ; // 1 hour default
}
async get ( key : string ) : Promise < object | undefined > {
const data = await this . redis . get ( key );
return data ? JSON . parse ( data ) : undefined ;
}
async set ( key : string , value : object ) : Promise < boolean > {
const serialized = JSON . stringify ( value );
await this . redis . setex ( key , this . ttl , serialized );
return true ;
}
async delete ( key : string ) : Promise < boolean > {
await this . redis . del ( key );
return true ;
}
async touch ( key : string ) : Promise < void > {
await this . redis . expire ( key , this . ttl );
}
}
const sessionManager = new SessionManager ({
storage: new RedisStorage ({
redis: { host: 'localhost' , port: 6379 },
ttl: 7200 // 2 hours
})
});
Usage Patterns
Counter Example
Track how many times a user has interacted with the bot:
vk . updates . on ( 'message_new' , sessionManager . middleware );
vk . updates . on ( 'message_new' , async ( context ) => {
if ( ! context . session . messageCount ) {
context . session . messageCount = 0 ;
}
context . session . messageCount += 1 ;
await context . send (
`This is message # ${ context . session . messageCount } from you`
);
});
User Preferences
Store user settings and preferences:
vk . updates . on ( 'message_new' , async ( context ) => {
if ( context . text ?. startsWith ( '/lang ' )) {
const lang = context . text . split ( ' ' )[ 1 ];
context . session . language = lang ;
await context . send ( `Language set to: ${ lang } ` );
return ;
}
const userLang = context . session . language || 'en' ;
const greeting = userLang === 'ru' ? 'Привет!' : 'Hello!' ;
await context . send ( greeting );
});
Collect information across multiple messages:
vk . updates . on ( 'message_new' , async ( context ) => {
const { session } = context ;
if ( context . text === '/register' ) {
session . registrationStep = 'name' ;
await context . send ( 'What is your name?' );
return ;
}
if ( session . registrationStep === 'name' ) {
session . userName = context . text ;
session . registrationStep = 'age' ;
await context . send ( 'How old are you?' );
return ;
}
if ( session . registrationStep === 'age' ) {
session . userAge = context . text ;
session . registrationStep = null ;
await context . send (
`Thanks! Name: ${ session . userName } , Age: ${ session . userAge } `
);
}
});
For complex multi-step workflows, consider using @vk-io/scenes instead.
Maintain a shopping cart across messages:
vk . updates . on ( 'message_new' , async ( context ) => {
if ( ! context . session . cart ) {
context . session . cart = [];
}
if ( context . text ?. startsWith ( '/add ' )) {
const item = context . text . substring ( 5 );
context . session . cart . push ( item );
await context . send ( `Added " ${ item } " to cart. Total items: ${ context . session . cart . length } ` );
}
if ( context . text === '/cart' ) {
if ( context . session . cart . length === 0 ) {
await context . send ( 'Your cart is empty' );
} else {
await context . send ( `Your cart: \n ${ context . session . cart . join ( ' \n ' ) } ` );
}
}
if ( context . text === '/clear' ) {
context . session . cart = [];
await context . send ( 'Cart cleared' );
}
});
Advanced Usage
Custom Storage Keys
Use different keys for different contexts:
const sessionManager = new SessionManager ({
getStorageKey : ( context ) => {
// Use peer ID for group chats, sender ID for private messages
if ( context . isChat ) {
return `chat_ ${ context . peerId } ` ;
}
return `user_ ${ context . senderId } ` ;
}
});
Multiple Session Types
Create separate session managers for different purposes:
const userSession = new SessionManager ({
contextKey: 'userSession' ,
getStorageKey : ( context ) => `user_ ${ context . senderId } `
});
const chatSession = new SessionManager ({
contextKey: 'chatSession' ,
getStorageKey : ( context ) => `chat_ ${ context . peerId } `
});
vk . updates . on ( 'message_new' , userSession . middleware );
vk . updates . on ( 'message_new' , chatSession . middleware );
vk . updates . on ( 'message_new' , async ( context ) => {
// Access both sessions
context . userSession . lastCommand = context . text ;
context . chatSession . messageCount = ( context . chatSession . messageCount || 0 ) + 1 ;
});
Force Update
Manually trigger session save using $forceUpdate():
vk . updates . on ( 'message_new' , async ( context ) => {
context . session . data = 'some value' ;
// Manually force save before async operation
await context . session . $forceUpdate ();
// Long async operation
await longRunningTask ();
});
TypeScript Support
Define session structure with TypeScript:
interface UserSession {
messageCount : number ;
language : string ;
cart : string [];
lastCommand ?: string ;
}
const sessionManager = new SessionManager < UserSession >();
vk . updates . on ( 'message_new' , sessionManager . middleware );
vk . updates . on ( 'message_new' , async ( context ) => {
// TypeScript knows the session structure
context . session . messageCount = ( context . session . messageCount || 0 ) + 1 ;
context . session . language = context . session . language || 'en' ;
});
Session Lifecycle
Request Received
A new message arrives and triggers the middleware chain
Load Session
Session manager loads existing session data from storage using the storage key
Attach to Context
Session data is wrapped in a Proxy and attached to the context
Handler Execution
Your message handler runs and can read/write to context.session
Auto-Save
After handler completes, changed session data is automatically saved to storage
Best Practices
Initialize Data Always check if session properties exist before using them if ( ! context . session . counter ) {
context . session . counter = 0 ;
}
Use Persistent Storage In production, use Redis or another persistent storage instead of memory
Set TTL Configure time-to-live for sessions to automatically clean up inactive users
Keep Sessions Small Store only essential data in sessions. Use a database for large datasets
Complete Example
import { VK } from 'vk-io' ;
import { SessionManager } from '@vk-io/session' ;
interface BotSession {
messageCount : number ;
userName ?: string ;
preferences : {
language : string ;
notifications : boolean ;
};
}
const vk = new VK ({ token: process . env . TOKEN });
const sessionManager = new SessionManager < BotSession >();
vk . updates . on ( 'message_new' , sessionManager . middleware );
vk . updates . on ( 'message_new' , async ( context ) => {
const { session } = context ;
// Initialize session
if ( ! session . messageCount ) {
session . messageCount = 0 ;
session . preferences = {
language: 'en' ,
notifications: true
};
}
session . messageCount += 1 ;
// Handle commands
if ( context . text === '/start' ) {
await context . send (
`Welcome! You've sent ${ session . messageCount } messages.`
);
return ;
}
if ( context . text ?. startsWith ( '/setname ' )) {
session . userName = context . text . substring ( 9 );
await context . send ( `Name set to: ${ session . userName } ` );
return ;
}
if ( context . text === '/profile' ) {
await context . send (
`Profile: \n ` +
`Name: ${ session . userName || 'Not set' } \n ` +
`Messages: ${ session . messageCount } \n ` +
`Language: ${ session . preferences . language } `
);
return ;
}
if ( context . text === '/reset' ) {
// Clear session by replacing it
context . session = {};
await context . send ( 'Session reset' );
return ;
}
});
vk . updates . start (). catch ( console . error );
Have you created a storage adapter? Open an issue to add it to this list!