/** * Hono Context Extension * * Type-safe context extension using c.set() and c.get() with custom Variables. */ import { Hono } from 'hono' import type { Context, Next } from 'hono' // ============================================================================ // TYPE DEFINITIONS // ============================================================================ // Define environment bindings (for Cloudflare Workers, etc.) type Bindings = { DATABASE_URL: string API_KEY: string ENVIRONMENT: 'development' | 'staging' | 'production' } // Define context variables (c.set/c.get) type Variables = { user: { id: string email: string name: string role: 'admin' | 'user' } requestId: string startTime: number logger: { info: (message: string, meta?: any) => void warn: (message: string, meta?: any) => void error: (message: string, meta?: any) => void } db: { query: (sql: string, params?: any[]) => Promise execute: (sql: string, params?: any[]) => Promise } cache: { get: (key: string) => Promise set: (key: string, value: string, ttl?: number) => Promise delete: (key: string) => Promise } } // Create typed app const app = new Hono<{ Bindings: Bindings; Variables: Variables }>() // ============================================================================ // REQUEST ID MIDDLEWARE // ============================================================================ app.use('*', async (c, next) => { const requestId = crypto.randomUUID() c.set('requestId', requestId) await next() c.res.headers.set('X-Request-ID', requestId) }) // ============================================================================ // PERFORMANCE TIMING MIDDLEWARE // ============================================================================ app.use('*', async (c, next) => { const startTime = Date.now() c.set('startTime', startTime) await next() const elapsed = Date.now() - startTime c.res.headers.set('X-Response-Time', `${elapsed}ms`) const logger = c.get('logger') logger.info(`Request completed in ${elapsed}ms`, { path: c.req.path, method: c.req.method, }) }) // ============================================================================ // LOGGER MIDDLEWARE // ============================================================================ app.use('*', async (c, next) => { const requestId = c.get('requestId') const logger = { info: (message: string, meta?: any) => { console.log( JSON.stringify({ level: 'info', requestId, message, ...meta, timestamp: new Date().toISOString(), }) ) }, warn: (message: string, meta?: any) => { console.warn( JSON.stringify({ level: 'warn', requestId, message, ...meta, timestamp: new Date().toISOString(), }) ) }, error: (message: string, meta?: any) => { console.error( JSON.stringify({ level: 'error', requestId, message, ...meta, timestamp: new Date().toISOString(), }) ) }, } c.set('logger', logger) await next() }) // ============================================================================ // DATABASE MIDDLEWARE // ============================================================================ app.use('/api/*', async (c, next) => { // Simulated database connection const db = { query: async (sql: string, params?: any[]): Promise => { const logger = c.get('logger') logger.info('Executing query', { sql, params }) // Simulated query execution return [] as T[] }, execute: async (sql: string, params?: any[]): Promise => { const logger = c.get('logger') logger.info('Executing statement', { sql, params }) // Simulated execution }, } c.set('db', db) await next() }) // ============================================================================ // CACHE MIDDLEWARE // ============================================================================ app.use('/api/*', async (c, next) => { // Simulated cache (use Redis, KV, etc. in production) const cacheStore = new Map() const cache = { get: async (key: string): Promise => { const logger = c.get('logger') const entry = cacheStore.get(key) if (!entry) { logger.info('Cache miss', { key }) return null } if (entry.expiresAt < Date.now()) { logger.info('Cache expired', { key }) cacheStore.delete(key) return null } logger.info('Cache hit', { key }) return entry.value }, set: async (key: string, value: string, ttl: number = 60000): Promise => { const logger = c.get('logger') logger.info('Cache set', { key, ttl }) cacheStore.set(key, { value, expiresAt: Date.now() + ttl, }) }, delete: async (key: string): Promise => { const logger = c.get('logger') logger.info('Cache delete', { key }) cacheStore.delete(key) }, } c.set('cache', cache) await next() }) // ============================================================================ // AUTHENTICATION MIDDLEWARE // ============================================================================ app.use('/api/*', async (c, next) => { const token = c.req.header('Authorization')?.replace('Bearer ', '') const logger = c.get('logger') if (!token) { logger.warn('Missing authentication token') return c.json({ error: 'Unauthorized' }, 401) } // Simulated token validation if (token !== 'valid-token') { logger.warn('Invalid authentication token') return c.json({ error: 'Invalid token' }, 401) } // Simulated user lookup const user = { id: '123', email: 'user@example.com', name: 'John Doe', role: 'user' as const, } c.set('user', user) logger.info('User authenticated', { userId: user.id }) await next() }) // ============================================================================ // ROUTES USING CONTEXT // ============================================================================ // Route using logger app.get('/api/log-example', (c) => { const logger = c.get('logger') logger.info('This is an info message') logger.warn('This is a warning') logger.error('This is an error') return c.json({ message: 'Logged' }) }) // Route using user app.get('/api/profile', (c) => { const user = c.get('user') return c.json({ user: { id: user.id, email: user.email, name: user.name, role: user.role, }, }) }) // Route using database app.get('/api/users', async (c) => { const db = c.get('db') const logger = c.get('logger') try { const users = await db.query<{ id: string; name: string }>('SELECT * FROM users') return c.json({ users }) } catch (error) { logger.error('Database query failed', { error }) return c.json({ error: 'Database error' }, 500) } }) // Route using cache app.get('/api/cached-data', async (c) => { const cache = c.get('cache') const logger = c.get('logger') const cacheKey = 'expensive-data' // Try to get from cache const cached = await cache.get(cacheKey) if (cached) { return c.json({ data: JSON.parse(cached), cached: true }) } // Simulate expensive computation const data = { result: 'expensive data', timestamp: Date.now() } // Store in cache await cache.set(cacheKey, JSON.stringify(data), 60000) // 1 minute return c.json({ data, cached: false }) }) // Route using request ID app.get('/api/request-info', (c) => { const requestId = c.get('requestId') const startTime = c.get('startTime') const elapsed = Date.now() - startTime return c.json({ requestId, elapsed: `${elapsed}ms`, method: c.req.method, path: c.req.path, }) }) // Route using environment bindings app.get('/api/env', (c) => { const environment = c.env.ENVIRONMENT const apiKey = c.env.API_KEY // Don't expose this in real app! return c.json({ environment, hasApiKey: !!apiKey, }) }) // ============================================================================ // COMBINING MULTIPLE CONTEXT VALUES // ============================================================================ app.post('/api/create-user', async (c) => { const logger = c.get('logger') const db = c.get('db') const user = c.get('user') const requestId = c.get('requestId') // Check permissions if (user.role !== 'admin') { logger.warn('Unauthorized user creation attempt', { userId: user.id, requestId, }) return c.json({ error: 'Forbidden' }, 403) } // Parse request body const body = await c.req.json() // Create user try { await db.execute('INSERT INTO users (name, email) VALUES (?, ?)', [body.name, body.email]) logger.info('User created', { createdBy: user.id, newUserEmail: body.email, requestId, }) return c.json({ success: true }, 201) } catch (error) { logger.error('User creation failed', { error, requestId, }) return c.json({ error: 'Failed to create user' }, 500) } }) // ============================================================================ // CUSTOM CONTEXT HELPERS // ============================================================================ // Helper to get authenticated user (with type guard) function getAuthenticatedUser(c: Context<{ Bindings: Bindings; Variables: Variables }>) { const user = c.get('user') if (!user) { throw new Error('User not authenticated') } return user } // Helper to check admin role function requireAdmin(c: Context<{ Bindings: Bindings; Variables: Variables }>) { const user = getAuthenticatedUser(c) if (user.role !== 'admin') { throw new Error('Admin access required') } return user } // Usage app.delete('/api/users/:id', (c) => { const admin = requireAdmin(c) // Throws if not admin const logger = c.get('logger') logger.info('User deletion requested', { adminId: admin.id, targetUserId: c.req.param('id'), }) return c.json({ success: true }) }) // ============================================================================ // EXPORT // ============================================================================ export default app export type { Bindings, Variables } export { getAuthenticatedUser, requireAdmin }