Context
Define shared state for all procedures
Context is the shared state passed to all procedure handlers. It allows you to inject dependencies like database connections, authentication info, or any other service your procedures need.
Static Context
The static context approach initializes dependencies at module load time. This works well when your services are already initialized before the server starts:
// src/api.ts
import { defineContext } from '@deessejs/server'
const db = await createDatabase() // Connection created here
const { t, createAPI } = defineContext({
context: {
db, // This exact object is the context
auth: authService,
},
})When to use:
- Your dependencies are already initialized (singletons, pools)
- You want simpler, more readable code
- The context doesn't need any setup logic
You pass an object directly. No function call happens - the reference is stored as-is.
Lazy Context (Factory)
The lazy context (factory) approach delays initialization until createAPI() is called. This is best for delaying expensive initialization:
// src/api.ts
import { defineContext } from '@deessejs/server'
const { t, createAPI } = defineContext({
createContext: () => ({
db: myDatabase, // Connection created when API is created
auth: authService,
}),
})When to use:
- You want to delay expensive initialization until the API starts
- You need setup logic (async initialization, validation)
- You want to keep initialization logic near the API definition
You pass a factory function that runs once when createAPI() is invoked. This delays resource creation until the API is actually initialized.
Initialization Timing
To understand when context is created, consider this example showing the timing of initialization:
// src/api.ts
const { createAPI } = defineContext({
createContext: () => {
console.log('Context created!') // This runs when createAPI() is called
return { db: myDatabase }
},
})
console.log('defineContext done') // Runs first
// Context not created yet!
export const api = createAPI({ router: appRouter })
// NOW "Context created!" printsThe createContext factory runs when you call createAPI(), not when you call defineContext(). This is useful if you want initialization to happen at the point where the server starts, rather than at module load time.
Type Safety
The context type is fully inferred from your definitions. This example shows how TypeScript automatically types your context:
// src/api.ts
import { defineContext } from '@deessejs/server'
import { ok } from '@deessejs/fp'
const { t, createAPI } = defineContext({
context: {
db: myDatabase,
user: null as { id: string; name: string } | null,
},
})
// Handler has full type inference
const getUser = t.query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
// ctx is fully typed: { db: Database; user: {...} | null }
const user = await ctx.db.users.find(args.id)
return ok(user)
},
})Best Practices
1. Keep Context Minimal
To keep context minimal, only include what's actually needed in handlers. This example demonstrates the contrast:
// Don't - unnecessary dependencies
context: {
logger: myLogger,
cache: redisCache,
db: database,
config: appConfig,
utils: helperFunctions,
}
// Do - only what handlers need
context: {
db: database,
auth: authService,
}2. Use Lazy Creation for Expensive Resources
To avoid expensive initialization at module load time, use lazy creation. This example shows the difference:
// Don't - connection at definition time
const db = await createDatabase()
context: {
db,
}
// Do - lazy factory
createContext: () => ({
db: myDatabase,
})3. Handle Missing Auth Gracefully
To handle missing authentication gracefully, initialize context with null user and let middleware enrich it. This example shows the pattern:
createContext: () => {
// Setup context with null user initially
// Auth enrichment should be handled via middleware or headers
return {
user: null,
}
}API Reference
Prop
Type
Common Patterns
Database Connection
To add a database connection to your context, define it in the factory function. This example shows the pattern:
// src/api.ts
import { defineContext } from '@deessejs/server'
const { t, createAPI } = defineContext({
createContext: () => ({
db: myDatabase,
}),
})Multiple Services
To add multiple services to your context, include each one in the context object. This example shows how to wire up several services:
// src/api.ts
import { defineContext } from '@deessejs/server'
const { t, createAPI } = defineContext({
context: {
db: mysqlDatabase,
cache: redisCache,
mailer: emailService,
},
})