Skip to content

Sessions

Shokupan provides session management compatible with connect/express-session stores.

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();
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'
}
}));

The default memory store is for development only:

app.use(Session({
secret: 'dev-secret'
// Uses memory store by default
}));
Terminal window
bun add connect-redis redis
import { 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
}
}));
Terminal window
bun add connect-mongo
import MongoStore from 'connect-mongo';
app.use(Session({
secret: process.env.SESSION_SECRET!,
store: MongoStore.create({
mongoUrl: process.env.MONGODB_URL!
})
}));
Terminal window
bun add connect-sqlite3
import SQLiteStore from 'connect-sqlite3';
const SQLiteSession = SQLiteStore(Session);
app.use(Session({
secret: process.env.SESSION_SECRET!,
store: new SQLiteSession({
db: 'sessions.db',
dir: './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 };
});
app.get('/cart', (ctx) => {
return {
cart: ctx.session.cart || []
};
});
app.post('/cart/clear', (ctx) => {
delete ctx.session.cart;
return { message: 'Cart cleared' };
});
app.post('/logout', (ctx) => {
ctx.session.destroy();
return { message: 'Logged out' };
});
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);
});
// Login
app.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 route
const 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;
});
// Logout
app.post('/logout', (ctx) => {
ctx.session.destroy();
return { message: 'Logged out' };
});
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 };
});
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 };
});
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
}
}));

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 safety
app.get('/profile', (ctx) => {
const userId = ctx.session.userId; // Typed as string | undefined
});