#
Bootstrap
The bootstrap system in QuickDapp is built around the ServerApp pattern - a dependency injection approach that provides clean access to all backend services. This replaces traditional singleton patterns with a more testable and maintainable architecture.
#
The ServerApp Pattern
The ServerApp
type defines all the core services your application needs:
export type ServerApp = {
app: Elysia // ElysiaJS server instance
db: PostgresJsDatabase // DrizzleORM database connection
rootLogger: Logger // Root logger instance
createLogger: typeof createLogger // Logger factory
workerManager: WorkerManager // Background job processing
socketManager: ISocketManager // WebSocket manager
publicClient: PublicClient // Blockchain read client
walletClient: WalletClient // Blockchain write client
createNotification: Function // Notification system
}
#
Bootstrap Process
The bootstrap process initializes all services in the correct order:
// src/server/bootstrap.ts
export async function createServerApp(options: BootstrapOptions): Promise<ServerApp> {
// 1. Load configuration
const config = serverConfig
// 2. Create logger
const rootLogger = createRootLogger()
// 3. Initialize database connection
const db = await dbManager.connect()
// 4. Create blockchain clients
const publicClient = createPublicClient({ ... })
const walletClient = createWalletClient({ ... })
// 5. Initialize WebSocket manager
const socketManager = new SocketManager(rootLogger)
// 6. Create worker manager (if requested)
const workerManager = options.includeWorkerManager
? new WorkerManager({ ... })
: undefined
// 7. Set up notification system
const createNotification = createNotificationFactory(socketManager)
return {
db,
rootLogger,
createLogger,
workerManager,
socketManager,
publicClient,
walletClient,
createNotification
}
}
#
Using ServerApp in Components
#
GraphQL Resolvers
GraphQL resolvers receive the ServerApp
through context:
export const resolvers = {
Query: {
tokens: async (parent, args, context) => {
const { serverApp, user } = context
// Access database through ServerApp
return await serverApp.db
.select()
.from(tokenTable)
.where(eq(tokenTable.ownerId, user.id))
}
},
Mutation: {
deployToken: async (parent, { input }, context) => {
const { serverApp, user } = context
// Submit background job
await serverApp.workerManager.submitJob({
type: 'deployToken',
data: { ...input, ownerId: user.id }
})
return { success: true }
}
}
}
#
Worker Jobs
Worker jobs receive ServerApp
as their first parameter:
// src/server/workers/jobs/deployToken.ts
export async function deployTokenJob(
serverApp: ServerApp,
job: Job<DeployTokenData>
) {
// Access all services through ServerApp
const logger = serverApp.createLogger('deploy-token')
logger.info('Deploying token', { data: job.data })
// Use blockchain client
const hash = await serverApp.walletClient.deployContract({
abi: ERC20_ABI,
bytecode: ERC20_BYTECODE,
args: [job.data.name, job.data.symbol]
})
// Save to database
await serverApp.db.insert(tokenTable).values({
address: receipt.contractAddress,
name: job.data.name,
symbol: job.data.symbol,
ownerId: job.data.ownerId
})
}
#
Custom Services
Create services that accept ServerApp
for dependency access:
// src/server/services/tokenService.ts
export class TokenService {
constructor(private serverApp: ServerApp) {}
async createToken(data: CreateTokenData) {
const logger = this.serverApp.createLogger('token-service')
// Use database
const [token] = await this.serverApp.db
.insert(tokenTable)
.values(data)
.returning()
// Send notification
await this.serverApp.createNotification({
userId: data.ownerId,
type: 'token_created',
data: { tokenId: token.id }
})
logger.info('Token created', { tokenId: token.id })
return token
}
}
#
Configuration Loading
The bootstrap system loads configuration using a layered approach:
// Environment loading order:
// 1. .env (base)
// 2. .env.{NODE_ENV} (environment-specific)
// 3. .env.{NODE_ENV}.local (environment-specific local)
// 4. .env.local (local overrides, except in test/production)
import { serverConfig } from '@shared/config/server'
// Type-safe access to all configuration
const dbUrl = serverConfig.DATABASE_URL
const port = serverConfig.PORT
const logLevel = serverConfig.LOG_LEVEL
#
Testing with ServerApp
The ServerApp pattern makes testing much easier:
// tests/helpers/createTestServer.ts
export async function createTestServerApp(): Promise<ServerApp> {
return createServerApp({
includeWorkerManager: false, // Disable workers in tests
config: {
...serverConfig,
DATABASE_URL: 'postgresql://postgres@localhost:5432/quickdapp_test'
}
})
}
// In your tests
describe('Token Service', () => {
let serverApp: ServerApp
beforeEach(async () => {
serverApp = await createTestServerApp()
await setupTestDatabase(serverApp.db)
})
it('creates tokens correctly', async () => {
const tokenService = new TokenService(serverApp)
const token = await tokenService.createToken({
name: 'Test Token',
symbol: 'TEST'
})
expect(token).toBeDefined()
})
})
#
Environment-Specific Bootstrap
Different environments may need different bootstrap configurations:
// Development: Full services including workers
const serverApp = await createServerApp({
includeWorkerManager: true,
workerCountOverride: 1
})
// Test: Minimal services, no workers
const serverApp = await createServerApp({
includeWorkerManager: false
})
// Production: Optimized for performance
const serverApp = await createServerApp({
includeWorkerManager: true,
workerCountOverride: 'cpus' // Auto-scale to CPU count
})
#
Benefits of the ServerApp Pattern
#
Type Safety
- All dependencies are typed
- No global state to manage
- Compile-time checks for service availability
#
Testability
- Easy to mock individual services
- Clean dependency injection
- Isolated testing environments
#
Maintainability
- Clear service boundaries
- Consistent access patterns
- Easy to add new services
#
Performance
- Services initialized once
- Connection pooling handled centrally
- Efficient resource sharing
The ServerApp pattern provides a solid foundation for building scalable, maintainable applications while keeping the architecture clean and testable.