πŸ‡―πŸ‡΅ Langfuse Cloud Japan is live β†’
HandbookCode Structure
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?