HandbookProduct EngineeringPlaybooksCode Structure

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 config

Worker Package (/worker/src/)

worker/src/
β”œβ”€β”€ queues/                # BullMQ job processors
β”‚   β”œβ”€β”€ evalQueue.ts
β”‚   β”œβ”€β”€ ingestionQueue.ts
β”‚   └── workerManager.ts
β”œβ”€β”€ features/              # Business logic
└── app.ts                 # Express server + queue setup

Shared 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 exports

TypeScript 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?