Backend Code Structure Guide
This guide explains how Langfuse’s backend is organized and how to write code that follows our established patterns.
Architecture at a Glance
Langfuse uses a monorepo structure with three main packages:
- web - Next.js 15 application (UI + tRPC API + Public REST API)
- worker - Express-based background job processor using BullMQ
- packages/shared - Shared code, types, and utilities used by both web and worker
API Request Flow
┌─ Web (NextJs): tRPC API ────┐ ┌── Web (NextJs): Public API ─┐
│ │ │ │
│ HTTP Request │ │ HTTP Request │
│ ↓ │ │ ↓ │
│ tRPC Procedure │ │ withMiddlewares + │
│ (protectedProjectProcedure)│ │ createAuthedProjectAPIRoute│
│ ↓ │ │ ↓ │
│ Service (business logic) │ │ Service (business logic) │
│ ↓ │ │ ↓ │
│ Prisma / ClickHouse │ │ Prisma / ClickHouse │
│ │ │ │
└─────────────────────────────┘ └─────────────────────────────┘
↓
[optional]: Publish to Redis BullMQ queue
↓
┌─ Worker (Express): BullMQ Queue Job ────────────────────────┐
│ │
│ BullMQ Queue Job │
│ ↓ │
│ Queue Processor (handles job) │
│ ↓ │
│ Service (business logic) │
│ ↓ │
│ Prisma / ClickHouse │
│ │
└─────────────────────────────────────────────────────────────┘We follow the layered architecture pattern:
- Router Layer: HTTP Requests or BullMQ Job handlers
- Service Layer: Contains all the business logic
- Repository Layer: Prisma / ClickHouse
Directory Structure
Web Package (/web/src/)
web/src/
├── features/ # Feature-organized code
│ └── [feature-name]/
│ ├── server/ # Backend: tRPC routers, services
│ ├── components/ # Frontend: React components
│ └── types/ # TypeScript types
│
├── server/
│ ├── api/
│ │ ├── routers/ # tRPC routers
│ │ ├── trpc.ts # tRPC config & middleware
│ │ └── root.ts # Root router
│ ├── auth.ts # NextAuth configuration
│ └── db.ts # Database client
│
├── pages/
│ ├── api/
│ │ ├── public/ # Public REST API endpoints
│ │ └── trpc/ # tRPC handler
│ └── [routes].tsx # Next.js pages
│
├── __tests__/ # Jest tests
├── instrumentation.ts # OpenTelemetry setup
└── env.mjs # Environment configWorker Package (/worker/src/)
worker/src/
├── queues/ # BullMQ job processors
│ ├── evalQueue.ts
│ ├── ingestionQueue.ts
│ └── workerManager.ts
├── features/ # Business logic
└── app.ts # Express server + queue setupShared Package (/packages/shared/src/)
shared/src/
├── server/ # Server-only code
│ ├── auth/ # Authentication utilities
│ ├── clickhouse/ # ClickHouse client
│ ├── repositories/ # Complex query logic
│ ├── services/ # Shared business logic
│ ├── redis/ # Queue and cache utilities
│ └── instrumentation/ # Observability helpers
│
├── encryption/ # Encryption utilities
├── tableDefinitions/ # Database schemas
├── utils/ # Shared utilities
├── db.ts # Prisma client
└── index.ts # Public exportsTypeScript Types
We use TypeScript for all our code and maintain a structured type system with clear conversion boundaries.
Type Hierarchy
Our type system follows a layered architecture with explicit conversions between layers.
Typing through the API layers
Typing through the storage layers
Key Differences:
- Public APIs are versioned - Our SDKs convert returned JSON to TypeScript/Python types. We must always be backwards compatible. Hence, we define dedicated types for the public API and convert domain objects to these types.
- tRPC API is not versioned - We deploy our backend and frontend in sync and force refresh our frontend on new deployments. Therefore we can introduce breaking changes to the tRPC API.
- The domain types can be found in
packages/shared/src/domain/index.ts
Was this page helpful?