Sessions
Shokupan provides session management compatible with connect/express-session stores.
Basic Usage
Section titled “Basic Usage”import { Shokupan, Session } from 'shokupan';
const app = new Shokupan();
app.use(Session({ secret: 'your-secret-key'}));
app.get('/login', (ctx) => { ctx.session.user = { id: '123', name: 'Alice' }; return { message: 'Logged in' };});
app.get('/profile', (ctx) => { if (!ctx.session.user) { return ctx.json({ error: 'Not authenticated' }, 401); } return ctx.session.user;});
app.get('/logout', (ctx) => { ctx.session.destroy(); return { message: 'Logged out' };});
app.listen();Configuration
Section titled “Configuration”app.use(Session({ secret: 'your-secret-key', // Required name: 'sessionId', // Cookie name (default: 'connect.sid') resave: false, // Don't save unchanged sessions saveUninitialized: false, // Don't create sessions until needed
cookie: { httpOnly: true, secure: true, // HTTPS only maxAge: 24 * 60 * 60 * 1000, // 24 hours sameSite: 'lax' }}));Session Stores
Section titled “Session Stores”Memory Store (Development)
Section titled “Memory Store (Development)”The default memory store is for development only:
app.use(Session({ secret: 'dev-secret' // Uses memory store by default}));Redis Store
Section titled “Redis Store”bun add connect-redis redisimport { Session } from 'shokupan';import RedisStore from 'connect-redis';import { createClient } from 'redis';
const redisClient = createClient();await redisClient.connect();
app.use(Session({ secret: process.env.SESSION_SECRET!, store: new RedisStore({ client: redisClient }), cookie: { maxAge: 24 * 60 * 60 * 1000 }}));MongoDB Store
Section titled “MongoDB Store”bun add connect-mongoimport MongoStore from 'connect-mongo';
app.use(Session({ secret: process.env.SESSION_SECRET!, store: MongoStore.create({ mongoUrl: process.env.MONGODB_URL! })}));SQLite Store
Section titled “SQLite Store”bun add connect-sqlite3import SQLiteStore from 'connect-sqlite3';
const SQLiteSession = SQLiteStore(Session);
app.use(Session({ secret: process.env.SESSION_SECRET!, store: new SQLiteSession({ db: 'sessions.db', dir: './data' })}));Session Methods
Section titled “Session Methods”Set Data
Section titled “Set Data”app.post('/cart/add', async (ctx) => { const { productId } = await ctx.body();
if (!ctx.session.cart) { ctx.session.cart = []; }
ctx.session.cart.push(productId);
return { cart: ctx.session.cart };});Get Data
Section titled “Get Data”app.get('/cart', (ctx) => { return { cart: ctx.session.cart || [] };});Delete Data
Section titled “Delete Data”app.post('/cart/clear', (ctx) => { delete ctx.session.cart; return { message: 'Cart cleared' };});Destroy Session
Section titled “Destroy Session”app.post('/logout', (ctx) => { ctx.session.destroy(); return { message: 'Logged out' };});Regenerate Session
Section titled “Regenerate Session”app.post('/login', async (ctx) => { const { username, password } = await ctx.body();
// Validate credentials const user = await validateUser(username, password);
if (user) { // Regenerate session ID (security best practice) await ctx.session.regenerate();
ctx.session.user = user; return { message: 'Logged in' }; }
return ctx.json({ error: 'Invalid credentials' }, 401);});Common Patterns
Section titled “Common Patterns”Authentication
Section titled “Authentication”// Loginapp.post('/login', async (ctx) => { const { email, password } = await ctx.body();
const user = await authenticateUser(email, password);
if (!user) { return ctx.json({ error: 'Invalid credentials' }, 401); }
ctx.session.userId = user.id; ctx.session.email = user.email;
return { user };});
// Protected routeconst requireAuth = async (ctx, next) => { if (!ctx.session.userId) { return ctx.json({ error: 'Unauthorized' }, 401); }
ctx.state.user = await getUserById(ctx.session.userId); return next();};
app.get('/profile', requireAuth, (ctx) => { return ctx.state.user;});
// Logoutapp.post('/logout', (ctx) => { ctx.session.destroy(); return { message: 'Logged out' };});Shopping Cart
Section titled “Shopping Cart”app.get('/cart', (ctx) => { return { items: ctx.session.cart || [] };});
app.post('/cart', async (ctx) => { const { productId, quantity } = await ctx.body();
if (!ctx.session.cart) { ctx.session.cart = []; }
ctx.session.cart.push({ productId, quantity });
return { cart: ctx.session.cart };});Flash Messages
Section titled “Flash Messages”app.post('/submit', async (ctx) => { // Process form
ctx.session.flash = { type: 'success', message: 'Form submitted successfully' };
return ctx.redirect('/dashboard');});
app.get('/dashboard', (ctx) => { const flash = ctx.session.flash; delete ctx.session.flash; // Remove after reading
return { flash };});Security Best Practices
Section titled “Security Best Practices”app.use(Session({ secret: process.env.SESSION_SECRET!, // Strong, random secret
resave: false, saveUninitialized: false,
cookie: { httpOnly: true, // Prevent XSS secure: process.env.NODE_ENV === 'production', // HTTPS only sameSite: 'strict', // CSRF protection maxAge: 60 * 60 * 1000 // 1 hour }}));TypeScript Types
Section titled “TypeScript Types”Type your session data:
import { ShokupanContext } from 'shokupan';
interface SessionData { userId?: string; email?: string; cart?: Array<{ productId: string; quantity: number }>;}
declare module 'shokupan' { interface ShokupanContext { session: SessionData & { destroy: () => void; regenerate: () => Promise<void>; }; }}
// Now you have type safetyapp.get('/profile', (ctx) => { const userId = ctx.session.userId; // Typed as string | undefined});Next Steps
Section titled “Next Steps”- Authentication - OAuth2 support
- Rate Limiting - Protect login endpoints
- Security Headers - Add security headers