A TypeScript library for managing and synchronizing WhatsApp Baileys credentials between Redis (cache/in-memory store) and a persistent database using MikroORM.
Inspired by the useRedisAuthState pattern, this library provides a reusable, production-ready solution for storing Baileys authentication state with both fast Redis caching and database persistence.
- Fast Access: Store Baileys credentials in Redis for quick retrieval
- Persistent Storage: Sync credentials to a database (PostgreSQL, MySQL, SQLite, etc.) via MikroORM
- Hybrid Approach: Prefer Redis, fall back to database, automatic cache warming
- Baileys-Optimized: Handle both
credsandkeys(SignalDataTypeMap) efficiently - Type-Safe: Full TypeScript support with strict type checking
- TTL Support: Automatic expiration of credentials in Redis
- Redis Memory Management: Automatic LRU (Least Recently Used) eviction with configurable limits to prevent memory overflow
- Extensible: Easy to add support for other ORM adapters or databases
npm install wa-auth-storeThis automatically installs:
ioredis- Redis client@mikro-orm/core- ORM framework
By default, no specific database driver is installed. Choose one based on your database:
For PostgreSQL:
npm install @mikro-orm/postgresqlFor MySQL:
npm install @mikro-orm/mysqlFor SQLite:
npm install @mikro-orm/sqlite| Package | Installed | Why |
|---|---|---|
wa-auth-store |
Auto | The library itself |
ioredis |
Auto | Redis operations (required) |
@mikro-orm/core |
Auto | Database ORM (required) |
@mikro-orm/postgresql |
Optional | PostgreSQL driver |
@mikro-orm/mysql |
Optional | MySQL driver |
@mikro-orm/sqlite |
Optional | SQLite driver |
The simplest way to integrate with Baileys - just pass the state directly:
import {
BaileysAuthStore,
BaileysRedisAdapter,
BaileysOrmAdapter,
useBaileysAuthState,
} from 'wa-auth-store';
import { makeWASocket } from '@whiskeysockets/baileys';
// Setup
const redisAdapter = new BaileysRedisAdapter('redis://localhost:6379');
await redisAdapter.connect();
const ormAdapter = new BaileysOrmAdapter(orm.em, CredentialsEntity);
const authStore = new BaileysAuthStore(redisAdapter, ormAdapter);
// Get ready-to-use state
const { state, saveCreds } = await useBaileysAuthState(authStore, 'session-123');
// Pass to Baileys - that's it!
const { socket } = makeWASocket({
auth: state,
printQRInTerminal: true,
});
// Save when credentials update
socket.ev.on('creds.update', saveCreds);This package is designed for multi-session WhatsApp management, but it works perfectly for single sessions too:
Multi-Session Example (manage multiple WhatsApp accounts):
// Each user gets their own session
const user1State = await useBaileysAuthState(authStore, 'user-123');
const user2State = await useBaileysAuthState(authStore, 'user-456');
const user3State = await useBaileysAuthState(authStore, 'user-789');Single Session Example (one WhatsApp account):
// Use phone number as session ID for a single account
const { state, saveCreds } = await useBaileysAuthState(authStore, '1234567890');
const { socket } = makeWASocket({
auth: state,
printQRInTerminal: true,
});
socket.ev.on('creds.update', saveCreds);The session ID is just a unique identifier - use whatever makes sense for your use case:
- User IDs:
'user-123','user-456' - Phone numbers:
'1234567890','9876543210' - Email addresses:
'user@example.com' - Custom identifiers:
'whatsapp-bot-1','support-team'
import { BaileysAuthStore, BaileysRedisAdapter } from 'wa-auth-store';
// Initialize Redis adapter
const redisAdapter = new BaileysRedisAdapter('redis://localhost:6379');
await redisAdapter.connect();
// Create auth store (Redis only)
const authStore = new BaileysAuthStore(redisAdapter);
// Save Baileys credentials
await authStore.saveCreds('session-123', {
creds: authenticationCreds, // Your Baileys AuthenticationCreds
keys: signalDataMap, // Your SignalDataTypeMap
});
// Retrieve credentials
const credential = await authStore.getCreds('session-123');
console.log(credential);
// Get specific keys
const appStateKeys = await authStore.getKeys('session-123', 'app-state-sync-key', [
'key-id-1',
'key-id-2',
]);
// Delete session
await authStore.deleteCreds('session-123');import { MikroORM } from '@mikro-orm/postgresql';
import { BaileysAuthStore, BaileysRedisAdapter, BaileysOrmAdapter } from 'wa-auth-store';
import { CredentialsEntity } from './entities/credentials.entity';
// Initialize MikroORM
const orm = await MikroORM.init({
entities: [CredentialsEntity],
dbName: 'baileys_db',
user: 'postgres',
password: 'password',
host: 'localhost',
});
// Initialize adapters
const redisAdapter = new BaileysRedisAdapter('redis://localhost:6379');
await redisAdapter.connect();
const ormAdapter = new BaileysOrmAdapter(orm.em, CredentialsEntity);
// Create auth store with both adapters
const authStore = new BaileysAuthStore(redisAdapter, ormAdapter);
// Save credentials (syncs to both Redis and PostgreSQL)
await authStore.saveCreds(
'session-123',
{
creds: authenticationCreds,
keys: signalDataMap,
},
{
syncToDatabase: true,
ttl: 3600, // 1 hour TTL in Redis
}
);
// Retrieve - prefers Redis, falls back to database
const credential = await authStore.getCreds('session-123');
// Cleanup
await redisAdapter.disconnect();
await orm.close();import { useWhatsAppWeb } from '@whiskeysockets/baileys';
import { BaileysAuthStore, BaileysRedisAdapter, BaileysOrmAdapter } from 'wa-auth-store';
const redisAdapter = new BaileysRedisAdapter();
await redisAdapter.connect();
const ormAdapter = new BaileysOrmAdapter(orm.em, CredentialsEntity);
const authStore = new BaileysAuthStore(redisAdapter, ormAdapter);
const { state, saveCreds } = useWhatsAppWeb({
auth: {
creds: (await authStore.getCreds('session-id'))?.creds,
keys: {
get: async (type, ids) => {
return authStore.getKeys('session-id', type, ids);
},
set: async (data) => {
await authStore.setKeys('session-id', data);
},
},
},
});
// Override saveCreds to use our store
const originalSaveCreds = saveCreds;
saveCreds = async () => {
await authStore.saveCreds('session-id', {
creds: state.creds,
keys: state.keys,
});
};The easiest way to integrate with Baileys. Returns a complete authentication state with automatic credential and key management.
const { state, saveCreds } = await useBaileysAuthState(authStore, sessionId, options?);Parameters:
authStore(BaileysAuthStore) - Initialized auth storesessionId(string) - Unique session identifieroptions(optional):ttl?: number- Redis TTL in secondssyncToDatabase?: boolean- Sync to database (default: true)
Returns:
{
state: AuthenticationState, // Ready for Baileys
saveCreds: () => Promise<void> // Call on creds.update
}Example:
const { state, saveCreds } = await useBaileysAuthState(authStore, 'session-123');
const { socket } = makeWASocket({ auth: state });
socket.ev.on('creds.update', saveCreds);Get credentials - prefers Redis, falls back to database.
- sessionId (string): Session identifier
- Returns: Promise<Partial | null>
Save credentials to both Redis and database.
- sessionId (string): Session identifier
- credential (Partial): Credential data with
credsand/orkeys - options (optional):
{ syncToDatabase?: boolean, ttl?: number } - Returns: Promise
Delete credentials from both Redis and database.
- sessionId (string): Session identifier
- Returns: Promise
Get specific keys - prefers Redis, falls back to database.
- sessionId (string): Session identifier
- type (string): Key type (e.g., 'app-state-sync-key', 'pre-key')
- ids (string[]): Key IDs to retrieve
- Returns: Promise<Record<string, unknown>>
Set keys in both Redis and database.
- sessionId (string): Session identifier
- data (Record<string, Record<string, unknown>>): Keys organized by type
- Returns: Promise
Delete specific keys from both Redis and database.
- sessionId (string): Session identifier
- fields (string[]): Field names to delete
- Returns: Promise
Clear all data for a session.
- sessionId (string): Session identifier
- Returns: Promise
Example MikroORM entity for PostgreSQL:
import { Entity, PrimaryKey, Property, Index, Unique } from '@mikro-orm/core';
@Entity()
export class CredentialsEntity {
@PrimaryKey()
id!: number;
@Property()
@Unique()
@Index()
sessionId!: string;
@Property({ type: 'jsonb', nullable: true })
creds?: unknown; // Baileys AuthenticationCreds
@Property({ type: 'jsonb', nullable: true })
keys?: Record<string, unknown>; // SignalDataTypeMap entries
@Property()
createdAt = new Date();
@Property({ onUpdate: () => new Date() })
updatedAt = new Date();
}To add support for a different ORM or database, extend the BaileysOrmAdapter class:
import { BaileysOrmAdapter } from 'wa-auth-store';
import type { BaileysCredential } from 'wa-auth-store';
export class CustomOrmAdapter extends BaileysOrmAdapter {
// Inherit all methods from BaileysOrmAdapter
// Customize as needed for your ORM
}For production environments with hundreds or thousands of users, Redis memory can quickly become saturated. The RedisMemoryManager implements intelligent LRU (Least Recently Used) eviction to keep Redis within a controlled memory range.
- Automatic Memory Monitoring: Periodically checks Redis memory usage
- LRU Eviction: Automatically removes least-recently-used sessions when memory threshold is exceeded
- Inactivity Cleanup: Removes sessions that haven't been accessed for a configurable period
- Graceful Fallback: Evicted credentials are automatically retrieved from PostgreSQL on next login
- Configurable Limits: Set max memory, eviction threshold, and inactivity timeout
import { RedisMemoryManager } from 'wa-auth-store';
const memoryConfig = {
maxMemoryBytes: 1024 * 1024 * 1024, // 1GB (default)
evictionThreshold: 80, // Trigger eviction at 80% (default)
ttlInactivity: 7 * 24 * 60 * 60, // 7 days (default)
checkIntervalMs: 5 * 60 * 1000, // Check every 5 minutes (default)
};
const memoryManager = new RedisMemoryManager(redisClient, memoryConfig);
await memoryManager.start();import { BaileysRedisAdapter, RedisMemoryManager } from 'wa-auth-store';
const redisAdapter = new BaileysRedisAdapter('redis://localhost:6379');
const memoryManager = new RedisMemoryManager(redisAdapter.redis, {
maxMemoryBytes: 2 * 1024 * 1024 * 1024, // 2GB
evictionThreshold: 75, // Evict at 75%
ttlInactivity: 3 * 24 * 60 * 60, // 3 days
});
await memoryManager.start();
// Track access on every credential operation
await memoryManager.trackAccess(sessionId, estimatedSize);
// Get memory statistics
const stats = await memoryManager.getStats();
console.log(stats);
// {
// memoryUsage: { usedBytes: 500000000, maxBytes: 2147483648, percentUsed: 23.3 },
// totalSessions: 1250,
// sessionMetadata: [...]
// }
// Stop monitoring when done
await memoryManager.stop();-
Access Tracking: Each time a credential is accessed, the manager records:
- Last access timestamp
- Access count
- Estimated size in bytes
-
Memory Monitoring: Every
checkIntervalMs, the manager:- Checks current Redis memory usage
- If usage >
evictionThreshold, triggers LRU eviction - Removes sessions not accessed for
ttlInactivityseconds
-
LRU Eviction: Sessions are sorted by last access time and removed oldest-first until memory drops below threshold
-
Fallback: When a session is needed again:
- If not in Redis (evicted), it's retrieved from PostgreSQL
- Automatically reloaded into Redis
- Access tracking resumes
Small deployments (< 100 users):
{
maxMemoryBytes: 512 * 1024 * 1024, // 512MB
evictionThreshold: 85,
ttlInactivity: 14 * 24 * 60 * 60, // 14 days
}Medium deployments (100-1000 users):
{
maxMemoryBytes: 2 * 1024 * 1024 * 1024, // 2GB
evictionThreshold: 80,
ttlInactivity: 7 * 24 * 60 * 60, // 7 days
}Large deployments (1000+ users):
{
maxMemoryBytes: 8 * 1024 * 1024 * 1024, // 8GB
evictionThreshold: 75,
ttlInactivity: 3 * 24 * 60 * 60, // 3 days
}npm installnpm run dev: Run in watch modenpm run build: Build for productionnpm run test: Run testsnpm run lint: Lint codenpm run lint:fix: Fix linting issuesnpm run format: Format code with Prettier
npm run test
npm run test:ui # Open UI dashboardMIT
Contributions are welcome! Please feel free to submit a Pull Request.