backend-dev-guidelines

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Backend Development Guidelines

后端开发指南

Purpose

目的

Establish consistency and best practices across Langfuse's backend packages (web, worker, packages/shared) using Next.js 14, tRPC, BullMQ, and TypeScript patterns.
使用Next.js 14、tRPC、BullMQ和TypeScript模式,在Langfuse的后端包(web、worker、packages/shared)中建立一致性和最佳实践。

When to Use This Skill

何时使用本指南

Automatically activates when working on:
  • Creating or modifying tRPC routers and procedures
  • Creating or modifying public API endpoints (REST)
  • Creating or modifying BullMQ queue consumers and producers
  • Building services with business logic
  • Authenticating API requests
  • Accessing resources based on entitlements
  • Implementing middleware (tRPC, NextAuth, public API)
  • Database operations with Prisma (PostgreSQL) or ClickHouse
  • Observability with OpenTelemetry, DataDog, logger, and traceException
  • Input validation with Zod v4
  • Environment configuration from env variables
  • Backend testing and refactoring

在进行以下工作时自动遵循本指南:
  • 创建或修改tRPC路由和流程
  • 创建或修改公共API端点(REST)
  • 创建或修改BullMQ队列消费者和生产者
  • 构建包含业务逻辑的服务
  • API请求认证
  • 基于权限访问资源
  • 实现中间件(tRPC、NextAuth、公共API)
  • 使用Prisma(PostgreSQL)或ClickHouse进行数据库操作
  • 使用OpenTelemetry、DataDog、日志工具和traceException实现可观测性
  • 使用Zod v4进行输入验证
  • 基于环境变量配置环境
  • 后端测试和重构

Quick Start

快速开始

UI: New tRPC Feature Checklist (Web)

UI:新增tRPC功能检查清单(Web)

  • Router: Define in
    features/[feature]/server/*Router.ts
  • Procedures: Use appropriate procedure type (protected, public)
  • Authentication: Use JWT authorization via middlewares.
  • Entitlement check: Access resources based on resource and role
  • Validation: Zod v4 schema for input
  • Service: Business logic in service file
  • Error handling: Use traceException wrapper
  • Tests: Unit + integration tests in
    __tests__/
  • Config: Access via env.mjs
  • 路由:在
    features/[feature]/server/*Router.ts
    中定义
  • 流程:使用合适的流程类型(受保护、公开)
  • 认证:通过中间件使用JWT授权
  • 权限检查:基于资源和角色访问资源
  • 验证:使用Zod v4 schema进行输入验证
  • 服务:业务逻辑编写在服务文件中
  • 错误处理:使用traceException包装器
  • 测试:在
    __tests__/
    中编写单元测试和集成测试
  • 配置:通过env.mjs访问配置

SDKs: New Public API Endpoint Checklist (Web)

SDK:新增公共API端点检查清单(Web)

  • Route file: Create in
    pages/api/public/
  • Wrapper: Use
    withMiddlewares
    +
    createAuthedProjectAPIRoute
  • Types: Define in
    features/public-api/types/
  • Authentication: Authorization via basic auth
  • Validation: Zod schemas for query/body/response
  • Versioning: Versioning in API path and Zod schemas for query/body/response
  • Fern API Docs: Update
    fern/apis/server/definition/
    to match TypeScript types
  • Tests: Add end-to-end test in
    __tests__/async/
  • 路由文件:在
    pages/api/public/
    中创建
  • 包装器:使用
    withMiddlewares
    +
    createAuthedProjectAPIRoute
  • 类型:在
    features/public-api/types/
    中定义
  • 认证:通过基础认证进行授权
  • 验证:为查询/请求体/响应使用Zod schema
  • 版本控制:在API路径和Zod schema中进行版本控制
  • Fern API文档:更新
    fern/apis/server/definition/
    以匹配TypeScript类型
  • 测试:在
    __tests__/async/
    中添加端到端测试

New Queue Processor Checklist (Worker)

新增队列处理器检查清单(Worker)

  • Processor: Create in
    worker/src/queues/
  • Queue types: Create queue types in
    packages/shared/src/server/queues
  • Service: Business logic in
    features/
    or
    worker/src/features/
  • Error handling: Distinguish between errors which should fail queue processing and errors which should result in a succeeded event.
  • Queue registration: Add to WorkerManager in app.ts
  • Tests: Add vitest tests in worker

  • 处理器:在
    worker/src/queues/
    中创建
  • 队列类型:在
    packages/shared/src/server/queues
    中创建队列类型
  • 服务:业务逻辑编写在
    features/
    worker/src/features/
  • 错误处理:区分应导致队列处理失败的错误和应标记为成功事件的错误
  • 队列注册:在app.ts中添加到WorkerManager
  • 测试:在worker中添加vitest测试

Architecture Overview

架构概述

Layered Architecture

分层架构

undefined
undefined

Web Package (Next.js 14)

Web Package (Next.js 14)

┌─ tRPC API ──────────────────┐ ┌── Public REST 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 Package (Express) ──────────────────────────────────┐ │ │ │ BullMQ Queue Job │ │ ↓ │ │ Queue Processor (handles job) │ │ ↓ │ │ Service (business logic) │ │ ↓ │ │ Prisma / ClickHouse │ │ │ └─────────────────────────────────────────────────────────────┘

**Key Principles:**

- **Web**: tRPC procedures for UI OR public API routes for SDKs → Services → Database
- **Worker**: Queue processors → Services → Database
- **packages/shared**: Shared code for Web and Worker

See [architecture-overview.md](architecture-overview.md) for complete details.

---
┌─ tRPC API ──────────────────┐ ┌── Public REST 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 Package (Express) ──────────────────────────────────┐ │ │ │ BullMQ Queue Job │ │ ↓ │ │ Queue Processor (handles job) │ │ ↓ │ │ Service (business logic) │ │ ↓ │ │ Prisma / ClickHouse │ │ │ └─────────────────────────────────────────────────────────────┘

**核心原则:**

- **Web**:UI使用tRPC流程,SDK使用公共API路由 → 服务 → 数据库
- **Worker**:队列处理器 → 服务 → 数据库
- **packages/shared**:Web和Worker共用的代码

完整细节请查看[architecture-overview.md](architecture-overview.md)。

---

Directory Structure

目录结构

Web Package (
/web/
)

Web包(
/web/

web/src/
├── features/                # Feature-organized code
│   ├── [feature-name]/
│   │   ├── server/          # Backend logic
│   │   │   ├── *Router.ts   # tRPC router
│   │   │   └── service.ts   # Business logic
│   │   ├── components/      # React components
│   │   └── types/           # Feature types
├── server/
│   ├── api/
│   │   ├── routers/         # tRPC routers
│   │   ├── trpc.ts          # tRPC setup & middleware
│   │   └── root.ts          # Main router
│   ├── auth.ts              # NextAuth.js config
│   └── db.ts                # Database client
├── pages/
│   ├── api/
│   │   ├── public/          # Public REST APIs
│   │   └── trpc/            # tRPC endpoint
│   └── [routes].tsx         # Next.js pages
├── __tests__/               # Jest tests
│   └── async/               # Integration tests
├── instrumentation.ts       # OpenTelemetry (FIRST IMPORT)
└── env.mjs                  # Environment config
web/src/
├── features/                # 按功能组织的代码
│   ├── [feature-name]/
│   │   ├── server/          # 后端逻辑
│   │   │   ├── *Router.ts   # tRPC路由
│   │   │   └── service.ts   # 业务逻辑
│   │   ├── components/      # React组件
│   │   └── types/           # 功能类型定义
├── server/
│   ├── api/
│   │   ├── routers/         # tRPC路由集合
│   │   ├── trpc.ts          # tRPC配置与中间件
│   │   └── root.ts          # 主路由
│   ├── auth.ts              # NextAuth.js配置
│   └── db.ts                # 数据库客户端
├── pages/
│   ├── api/
│   │   ├── public/          # 公共REST API
│   │   └── trpc/            # tRPC端点
│   └── [routes].tsx         # Next.js页面
├── __tests__/               # Jest测试
│   └── async/               # 集成测试
├── instrumentation.ts       # OpenTelemetry(必须第一个导入)
└── env.mjs                  # 环境配置

Worker Package (
/worker/
)

Worker包(
/worker/

worker/src/
├── queues/                  # BullMQ processors
│   ├── evalQueue.ts
│   ├── ingestionQueue.ts
│   └── workerManager.ts
├── features/                # Business logic
│   └── [feature]/
│       └── service.ts
├── instrumentation.ts       # OpenTelemetry (FIRST IMPORT)
├── app.ts                   # Express setup + queue registration
├── env.ts                   # Environment config
└── index.ts                 # Server start
worker/src/
├── queues/                  # BullMQ处理器
│   ├── evalQueue.ts
│   ├── ingestionQueue.ts
│   └── workerManager.ts
├── features/                # 业务逻辑
│   └── [feature]/
│       └── service.ts
├── instrumentation.ts       # OpenTelemetry(必须第一个导入)
├── app.ts                   # Express配置 + 队列注册
├── env.ts                   # 环境配置
└── index.ts                 # 服务启动入口

Shared Package (
/packages/shared/
)

共享包(
/packages/shared/

shared/src/
├── server/                  # Server utilities
│   ├── auth/                # Authentication helpers
│   ├── clickhouse/          # ClickHouse client & schema
│   ├── instrumentation/     # OpenTelemetry helpers
│   ├── llm/                 # LLM integration utilities
│   ├── redis/               # Redis queues & cache
│   ├── repositories/        # Data repositories
│   ├── services/            # Shared services
│   ├── utils/               # Server utilities
│   ├── logger.ts
│   └── queues.ts
├── encryption/              # Encryption utilities
├── features/                # Feature-specific code
├── tableDefinitions/        # Table schemas
├── utils/                   # Shared utilities
├── constants.ts
├── db.ts                    # Prisma client
├── env.ts                   # Environment config
└── index.ts                 # Main exports
Import Paths (package.json exports):
The shared package exposes specific import paths for different use cases:
Import PathMaps ToUse For
@langfuse/shared
dist/src/index.js
General types, schemas, utilities, constants
@langfuse/shared/src/db
dist/src/db.js
Prisma client and database types
@langfuse/shared/src/server
dist/src/server/index.js
Server-side utilities (queues, auth, services, instrumentation)
@langfuse/shared/src/server/auth/apiKeys
dist/src/server/auth/apiKeys.js
API key management utilities
@langfuse/shared/encryption
dist/src/encryption/index.js
Encryption and signature utilities
Usage Examples:
typescript
// General imports - types, schemas, constants, interfaces
import {
  CloudConfigSchema,
  StringNoHTML,
  AnnotationQueueObjectType,
  type APIScoreV2,
  type ColumnDefinition,
  Role,
} from "@langfuse/shared";

// Database - Prisma client and types
import { prisma, Prisma, JobExecutionStatus } from "@langfuse/shared/src/db";
import { type DB as Database } from "@langfuse/shared";

// Server utilities - queues, services, auth, instrumentation
import {
  logger,
  instrumentAsync,
  traceException,
  redis,
  getTracesTable,
  StorageService,
  sendMembershipInvitationEmail,
  invalidateApiKeysForProject,
  recordIncrement,
  recordHistogram,
} from "@langfuse/shared/src/server";

// API key management (specific path)
import { createAndAddApiKeysToDb } from "@langfuse/shared/src/server/auth/apiKeys";

// Encryption utilities
import { encrypt, decrypt, sign, verify } from "@langfuse/shared/encryption";
What Goes Where:
The shared package provides types, utilities, and server code used by both web and worker packages. It has 5 export paths that control frontend vs backend access:
Import PathUsageWhat's Included
@langfuse/shared
✅ Frontend + BackendPrisma types, Zod schemas, constants, table definitions, domain models, utilities
@langfuse/shared/src/db
🔒 Backend onlyPrisma client instance
@langfuse/shared/src/server
🔒 Backend onlyServices, repositories, queues, auth, ClickHouse, LLM integration, instrumentation
@langfuse/shared/src/server/auth/apiKeys
🔒 Backend onlyAPI key management (separated to avoid circular deps)
@langfuse/shared/encryption
🔒 Backend onlyDatabase field encryption/decryption
Naming Conventions:
  • tRPC Routers:
    camelCaseRouter.ts
    -
    datasetRouter.ts
  • Services:
    service.ts
    in feature directory
  • Queue Processors:
    camelCaseQueue.ts
    -
    evalQueue.ts
  • Public APIs:
    kebab-case.ts
    -
    dataset-items.ts

shared/src/
├── server/                  # 服务端工具
│   ├── auth/                # 认证助手
│   ├── clickhouse/          # ClickHouse客户端与schema
│   ├── instrumentation/     # OpenTelemetry助手
│   ├── llm/                 # LLM集成工具
│   ├── redis/               # Redis队列与缓存
│   ├── repositories/        # 数据仓库
│   ├── services/            # 共享服务
│   ├── utils/               # 服务端工具
│   ├── logger.ts
│   └── queues.ts
├── encryption/              # 加密工具
├── features/                # 功能特定代码
├── tableDefinitions/        # 表结构定义
├── utils/                   # 共享工具
├── constants.ts
├── db.ts                    # Prisma客户端
├── env.ts                   # 环境配置
└── index.ts                 # 主导出文件
导入路径(package.json exports):
共享包为不同使用场景暴露了特定的导入路径:
导入路径对应路径用途
@langfuse/shared
dist/src/index.js
通用类型、schema、工具、常量
@langfuse/shared/src/db
dist/src/db.js
Prisma客户端和数据库类型
@langfuse/shared/src/server
dist/src/server/index.js
服务端工具(队列、认证、服务、埋点)
@langfuse/shared/src/server/auth/apiKeys
dist/src/server/auth/apiKeys.js
API密钥管理工具
@langfuse/shared/encryption
dist/src/encryption/index.js
加密与签名工具
使用示例:
typescript
// 通用导入 - 类型、schema、常量、接口
import {
  CloudConfigSchema,
  StringNoHTML,
  AnnotationQueueObjectType,
  type APIScoreV2,
  type ColumnDefinition,
  Role,
} from "@langfuse/shared";

// 数据库 - Prisma客户端和类型
import { prisma, Prisma, JobExecutionStatus } from "@langfuse/shared/src/db";
import { type DB as Database } from "@langfuse/shared";

// 服务端工具 - 队列、服务、认证、埋点
import {
  logger,
  instrumentAsync,
  traceException,
  redis,
  getTracesTable,
  StorageService,
  sendMembershipInvitationEmail,
  invalidateApiKeysForProject,
  recordIncrement,
  recordHistogram,
} from "@langfuse/shared/src/server";

// API密钥管理(特定路径)
import { createAndAddApiKeysToDb } from "@langfuse/shared/src/server/auth/apiKeys";

// 加密工具
import { encrypt, decrypt, sign, verify } from "@langfuse/shared/encryption";
代码存放规则:
共享包提供Web和Worker包共用的类型、工具和服务端代码。它有5个导出路径,用于控制前端和后端的访问权限:
导入路径适用场景包含内容
@langfuse/shared
✅ 前端 + 后端Prisma类型、Zod schema、常量、表结构定义、领域模型、工具
@langfuse/shared/src/db
🔒 仅后端Prisma客户端实例
@langfuse/shared/src/server
🔒 仅后端服务、数据仓库、队列、认证、ClickHouse、LLM集成、埋点
@langfuse/shared/src/server/auth/apiKeys
🔒 仅后端API密钥管理(分离以避免循环依赖)
@langfuse/shared/encryption
🔒 仅后端数据库字段加密/解密
命名规范:
  • tRPC路由:
    小驼峰Router.ts
    -
    datasetRouter.ts
  • 服务:功能目录下的
    service.ts
  • 队列处理器:
    小驼峰Queue.ts
    -
    evalQueue.ts
  • 公共API:
    短横线分隔.ts
    -
    dataset-items.ts

Core Principles

核心原则

1. tRPC Procedures Delegate to Services

1. tRPC流程委托给服务

typescript
// ❌ NEVER: Business logic in procedures
export const traceRouter = createTRPCRouter({
  byId: protectedProjectProcedure
    .input(z.object({ traceId: z.string() }))
    .query(async ({ input, ctx }) => {
      // 200 lines of logic here
    }),
});

// ✅ ALWAYS: Delegate to service
export const traceRouter = createTRPCRouter({
  byId: protectedProjectProcedure
    .input(z.object({ traceId: z.string() }))
    .query(async ({ input, ctx }) => {
      return await getTraceById(input.traceId);
    }),
});
typescript
// ❌ 错误示例:业务逻辑写在流程中
export const traceRouter = createTRPCRouter({
  byId: protectedProjectProcedure
    .input(z.object({ traceId: z.string() }))
    .query(async ({ input, ctx }) => {
      // 200行逻辑写在这里
    }),
});

// ✅ 正确示例:委托给服务
export const traceRouter = createTRPCRouter({
  byId: protectedProjectProcedure
    .input(z.object({ traceId: z.string() }))
    .query(async ({ input, ctx }) => {
      return await getTraceById(input.traceId);
    }),
});

2. Access Config via env.mjs, NEVER process.env

2. 通过env.mjs访问配置,禁止直接使用process.env

typescript
// ❌ NEVER (except in env.mjs itself)
const dbUrl = process.env.DATABASE_URL;

// ✅ ALWAYS
import { env } from "@/src/env.mjs";
const dbUrl = env.DATABASE_URL;
typescript
// ❌ 错误示例(除了env.mjs本身)
const dbUrl = process.env.DATABASE_URL;

// ✅ 正确示例
import { env } from "@/src/env.mjs";
const dbUrl = env.DATABASE_URL;

3. Validate ALL Input with Zod v4

3. 使用Zod v4验证所有输入

typescript
import { z } from "zod/v4";

const schema = z.object({
  email: z.string().email(),
  projectId: z.string(),
});
const validated = schema.parse(input);
typescript
import { z } from "zod/v4";

const schema = z.object({
  email: z.string().email(),
  projectId: z.string(),
});
const validated = schema.parse(input);

4. Services Use Prisma Directly for Simple CRUD or Repositories for Complex Queries

4. 简单CRUD操作直接使用Prisma,复杂查询使用数据仓库

typescript
// Services use Prisma directly for simple CRUD
import { prisma } from "@langfuse/shared/src/db";

const dataset = await prisma.dataset.findUnique({
  where: { id: datasetId, projectId }, // Always filter by projectId for tenant isolation
});

// Or use repositories for complex queries (traces, observations, scores)
import { getTracesTable } from "@langfuse/shared/src/server";

const traces = await getTracesTable({
  projectId,
  filter: [...],
  limit: 1000,
});
typescript
// 简单CRUD操作直接使用Prisma
import { prisma } from "@langfuse/shared/src/db";

const dataset = await prisma.dataset.findUnique({
  where: { id: datasetId, projectId }, // 必须通过projectId实现租户隔离
});

// 复杂查询(追踪、观测、评分)使用数据仓库
import { getTracesTable } from "@langfuse/shared/src/server";

const traces = await getTracesTable({
  projectId,
  filter: [...],
  limit: 1000,
});

6. Observability: OpenTelemetry + DataDog (Not Sentry for Backend)

6. 可观测性:OpenTelemetry + DataDog(后端不使用Sentry)

Langfuse uses OpenTelemetry for backend observability, with traces and logs sent to DataDog.
typescript
// Import observability utilities
import {
  logger,          // Winston logger with OpenTelemetry/DataDog context
  traceException,  // Record exceptions to OpenTelemetry spans
  instrumentAsync, // Create instrumented spans
} from "@langfuse/shared/src/server";

// Structured logging (includes trace_id, span_id, dd.trace_id)
logger.info("Processing dataset", { datasetId, projectId });
logger.error("Failed to create dataset", { error: err.message });

// Record exceptions to OpenTelemetry (sent to DataDog)
try {
  await operation();
} catch (error) {
  traceException(error); // Records to current span
  throw error;
}

// Instrument critical operations (all API routes auto-instrumented)
const result = await instrumentAsync(
  { name: "dataset.create" },
  async (span) => {
    span.setAttributes({ datasetId, projectId });
    // Operation here
    return dataset;
  },
);
Note: Frontend uses Sentry, but backend (tRPC, API routes, services, worker) uses OpenTelemetry + DataDog.
Langfuse使用OpenTelemetry实现后端可观测性,追踪和日志发送到DataDog。
typescript
// 导入可观测性工具
import {
  logger,          // 带有OpenTelemetry/DataDog上下文的Winston日志工具
  traceException,  // 记录异常到OpenTelemetry span
  instrumentAsync, // 创建埋点span
} from "@langfuse/shared/src/server";

// 结构化日志(包含trace_id、span_id、dd.trace_id)
logger.info("Processing dataset", { datasetId, projectId });
logger.error("Failed to create dataset", { error: err.message });

// 记录异常到OpenTelemetry(发送到DataDog)
try {
  await operation();
} catch (error) {
  traceException(error); // 记录到当前span
  throw error;
}

// 为关键操作添加埋点(所有API路由自动埋点)
const result = await instrumentAsync(
  { name: "dataset.create" },
  async (span) => {
    span.setAttributes({ datasetId, projectId });
    // 业务操作
    return dataset;
  },
);
注意:前端使用Sentry,但后端(tRPC、API路由、服务、worker)使用OpenTelemetry + DataDog。

7. Comprehensive Testing Required

7. 必须进行全面测试

Write tests for all new features and bug fixes. See testing-guide.md for detailed examples.
Test Types:
TypeFrameworkLocationPurpose
IntegrationJest
web/src/__tests__/async/
Full API endpoint testing
tRPCJest
web/src/__tests__/async/
tRPC procedures with auth
ServiceJest
web/src/__tests__/async/repositories/
Repository/service functions
WorkerVitest
worker/src/__tests__/
Queue processors & streams
Quick Examples:
typescript
// Integration Test (Public API)
const res = await makeZodVerifiedAPICall(
  PostDatasetsV1Response, "POST", "/api/public/datasets",
  { name: "test-dataset" }, auth
);
expect(res.status).toBe(200);

// tRPC Test
const { caller } = await prepare(); // Creates session + caller
const response = await caller.automations.getAutomations({ projectId });
expect(response).toHaveLength(1);

// Service Test
const result = await getObservationsWithModelDataFromEventsTable({
  projectId, filter: [...], limit: 1000, offset: 0
});
expect(result.length).toBeGreaterThan(0);

// Worker Test (vitest)
const stream = await getObservationStream({ projectId, filter: [] });
const rows = [];
for await (const chunk of stream) rows.push(chunk);
expect(rows).toHaveLength(2);
Key Principles:
  • Use unique IDs (
    randomUUID()
    ) to avoid test interference
  • Clean up test data or use unique project IDs
  • Tests must be independent and runnable in any order
  • Never use
    pruneDatabase
    in tests
为所有新功能和bug修复编写测试。详细示例请查看testing-guide.md
测试类型:
类型框架位置目的
集成测试Jest
web/src/__tests__/async/
完整API端点测试
tRPC测试Jest
web/src/__tests__/async/
带认证的tRPC流程测试
服务测试Jest
web/src/__tests__/async/repositories/
数据仓库/服务函数测试
Worker测试Vitest
worker/src/__tests__/
队列处理器与流测试
快速示例:
typescript
// 集成测试(公共API)
const res = await makeZodVerifiedAPICall(
  PostDatasetsV1Response, "POST", "/api/public/datasets",
  { name: "test-dataset" }, auth
);
expect(res.status).toBe(200);

// tRPC测试
const { caller } = await prepare(); // 创建会话和调用者
const response = await caller.automations.getAutomations({ projectId });
expect(response).toHaveLength(1);

// 服务测试
const result = await getObservationsWithModelDataFromEventsTable({
  projectId, filter: [...], limit: 1000, offset: 0
});
expect(result.length).toBeGreaterThan(0);

// Worker测试(vitest)
const stream = await getObservationStream({ projectId, filter: [] });
const rows = [];
for await (const chunk of stream) rows.push(chunk);
expect(rows).toHaveLength(2);
核心原则:
  • 使用唯一ID(
    randomUUID()
    )避免测试干扰
  • 清理测试数据或使用唯一项目ID
  • 测试必须独立,可按任意顺序运行
  • 测试中禁止使用
    pruneDatabase

8. Always Filter by projectId for Tenant Isolation

8. 始终通过projectId过滤实现租户隔离

typescript
// ✅ CORRECT: Filter by projectId for tenant isolation
const trace = await prisma.trace.findUnique({
  where: { id: traceId, projectId }, // Required for multi-tenant data isolation
});

// ✅ CORRECT: ClickHouse queries also require projectId
const traces = await queryClickhouse({
  query: `
    SELECT * FROM traces
    WHERE project_id = {projectId: String}
    AND timestamp >= {startTime: DateTime64(3)}
  `,
  params: { projectId, startTime },
});
typescript
// ✅ 正确示例:通过projectId过滤实现租户隔离
const trace = await prisma.trace.findUnique({
  where: { id: traceId, projectId }, // 多租户数据隔离必须设置
});

// ✅ 正确示例:ClickHouse查询也需要projectId
const traces = await queryClickhouse({
  query: `
    SELECT * FROM traces
    WHERE project_id = {projectId: String}
    AND timestamp >= {startTime: DateTime64(3)}
  `,
  params: { projectId, startTime },
});

9. Keep Fern API Definitions in Sync with TypeScript Types

9. 保持Fern API定义与TypeScript类型同步

When modifying public API types in
web/src/features/public-api/types/
, the corresponding Fern API definitions in
fern/apis/server/definition/
must be updated to match.
Zod to Fern Type Mapping:
Zod TypeFern TypeExample
.nullish()
optional<nullable<T>>
z.string().nullish()
optional<nullable<string>>
.nullable()
nullable<T>
z.string().nullable()
nullable<string>
.optional()
optional<T>
z.string().optional()
optional<string>
Always present
T
z.string()
string
Source References:
Add a comment at the top of each Fern type referencing the TypeScript source file:
yaml
undefined
修改
web/src/features/public-api/types/
中的公共API类型时,必须同步更新
fern/apis/server/definition/
中对应的Fern API定义。
Zod到Fern类型映射:
Zod类型Fern类型示例
.nullish()
optional<nullable<T>>
z.string().nullish()
optional<nullable<string>>
.nullable()
nullable<T>
z.string().nullable()
nullable<string>
.optional()
optional<T>
z.string().optional()
optional<string>
必填
T
z.string()
string
来源引用:
在每个Fern类型顶部添加注释,引用对应的TypeScript源文件:
yaml
undefined

Source: web/src/features/public-api/types/traces.ts - APITrace

Source: web/src/features/public-api/types/traces.ts - APITrace

Trace: properties: id: string name: type: nullable<string>

---
Trace: properties: id: string name: type: nullable<string>

---

Common Imports

常用导入

typescript
// tRPC (Web)
import { z } from "zod/v4";
import {
  createTRPCRouter,
  protectedProjectProcedure,
} from "@/src/server/api/trpc";
import { TRPCError } from "@trpc/server";

// Database
import { prisma } from "@langfuse/shared/src/db";
import type { Prisma } from "@prisma/client";

// ClickHouse
import {
  queryClickhouse,
  queryClickhouseStream,
  upsertClickhouse,
} from "@langfuse/shared/src/server";

// Observability - OpenTelemetry + DataDog (NOT Sentry for backend)
import {
  logger,          // Winston logger with OTEL/DataDog trace context
  traceException,  // Record exceptions to OpenTelemetry spans
  instrumentAsync, // Create instrumented spans for operations
} from "@langfuse/shared/src/server";

// Config
import { env } from "@/src/env.mjs"; // web
// or
import { env } from "./env"; // worker

// Public API (Web)
import { withMiddlewares } from "@/src/features/public-api/server/withMiddlewares";
import { createAuthedProjectAPIRoute } from "@/src/features/public-api/server/createAuthedProjectAPIRoute";

// Queue Processing (Worker)
import { Job } from "bullmq";
import { QueueName, TQueueJobTypes } from "@langfuse/shared/src/server";

typescript
// tRPC(Web)
import { z } from "zod/v4";
import {
  createTRPCRouter,
  protectedProjectProcedure,
} from "@/src/server/api/trpc";
import { TRPCError } from "@trpc/server";

// 数据库
import { prisma } from "@langfuse/shared/src/db";
import type { Prisma } from "@prisma/client";

// ClickHouse
import {
  queryClickhouse,
  queryClickhouseStream,
  upsertClickhouse,
} from "@langfuse/shared/src/server";

// 可观测性 - OpenTelemetry + DataDog(后端不使用Sentry)
import {
  logger,          // 带有OTEL/DataDog追踪上下文的Winston日志工具
  traceException,  // 记录异常到OpenTelemetry span
  instrumentAsync, // 为操作创建埋点span
} from "@langfuse/shared/src/server";

// 配置
import { env } from "@/src/env.mjs"; // Web端
// 或
import { env } from "./env"; // Worker端

// 公共API(Web)
import { withMiddlewares } from "@/src/features/public-api/server/withMiddlewares";
import { createAuthedProjectAPIRoute } from "@/src/features/public-api/server/createAuthedProjectAPIRoute";

// 队列处理(Worker)
import { Job } from "bullmq";
import { QueueName, TQueueJobTypes } from "@langfuse/shared/src/server";

Quick Reference

快速参考

HTTP Status Codes

HTTP状态码

CodeUse Case
200Success
201Created
400Bad Request
401Unauthorized
403Forbidden
404Not Found
500Server Error
状态码使用场景
200成功
201创建完成
400错误请求
401未授权
403禁止访问
404资源不存在
500服务器错误

Example Features to Reference

参考示例功能

Reference existing Langfuse features for implementation patterns:
  • Datasets (
    web/src/features/datasets/
    ) - Complete feature with tRPC router, public API, and service
  • Prompts (
    web/src/features/prompts/
    ) - Feature with versioning and templates
  • Evaluations (
    web/src/features/evals/
    ) - Complex feature with worker integration
  • Public API (
    web/src/features/public-api/
    ) - Middleware and route patterns

参考Langfuse现有功能的实现模式:
  • Datasets
    web/src/features/datasets/
    )- 包含tRPC路由、公共API和服务的完整功能
  • Prompts
    web/src/features/prompts/
    )- 带有版本控制和模板的功能
  • Evaluations
    web/src/features/evals/
    )- 集成Worker的复杂功能
  • Public API
    web/src/features/public-api/
    )- 中间件和路由模式

Anti-Patterns to Avoid

需避免的反模式

❌ Business logic in routes/procedures ❌ Direct process.env usage (always use env.mjs/env.ts) ❌ Missing error handling ❌ No input validation (always use Zod v4) ❌ Missing projectId filter on tenant-scoped queries ❌ console.log instead of logger/traceException (OpenTelemetry)

❌ 业务逻辑写在路由/流程中 ❌ 直接使用process.env(始终使用env.mjs/env.ts) ❌ 缺少错误处理 ❌ 未进行输入验证(始终使用Zod v4) ❌ 租户范围查询未添加projectId过滤 ❌ 使用console.log替代logger/traceException(OpenTelemetry)

Navigation Guide

导航指南

Need to...Read this
Understand architecturearchitecture-overview.md
Create routes/controllersrouting-and-controllers.md
Organize business logicservices-and-repositories.md
Create middlewaremiddleware-guide.md
Database accessdatabase-patterns.md
Manage configconfiguration.md
Write teststesting-guide.md

需要...查看文档
理解架构architecture-overview.md
创建路由/控制器routing-and-controllers.md
组织业务逻辑services-and-repositories.md
创建中间件middleware-guide.md
数据库访问database-patterns.md
管理配置configuration.md
编写测试testing-guide.md

Resource Files

资源文件

architecture-overview.md

architecture-overview.md

Three-layer architecture (tRPC/Public API → Services → Data Access), request lifecycle for tRPC/Public API/Worker, Next.js 14 directory structure, dual database system (PostgreSQL + ClickHouse), separation of concerns, repository pattern for complex queries
三层架构(tRPC/公共API → 服务 → 数据访问)、tRPC/公共API/Worker的请求生命周期、Next.js 14目录结构、双数据库系统(PostgreSQL + ClickHouse)、关注点分离、复杂查询的数据仓库模式

routing-and-controllers.md

routing-and-controllers.md

Next.js file-based routing, tRPC router patterns, Public REST API routes, layered architecture (Entry Points → Services → Repositories → Database), service layer organization, anti-patterns to avoid
Next.js文件路由、tRPC路由模式、公共REST API路由、分层架构(入口 → 服务 → 数据仓库 → 数据库)、服务层组织、需避免的反模式

services-and-repositories.md

services-and-repositories.md

Service layer overview, dependency injection patterns, singleton patterns, repository pattern for data access, service design principles, caching strategies, testing services
服务层概述、依赖注入模式、单例模式、数据访问的数据仓库模式、服务设计原则、缓存策略、服务测试

middleware-guide.md

middleware-guide.md

tRPC middleware (withErrorHandling, withOtelInstrumentation, enforceUserIsAuthed), seven tRPC procedure types (publicProcedure, authenticatedProcedure, protectedProjectProcedure, etc.), Public API middleware (withMiddlewares, createAuthedProjectAPIRoute), authentication patterns (NextAuth for tRPC, Basic Auth for Public API)
tRPC中间件(withErrorHandling、withOtelInstrumentation、enforceUserIsAuthed)、七种tRPC流程类型(publicProcedure、authenticatedProcedure、protectedProjectProcedure等)、公共API中间件(withMiddlewares、createAuthedProjectAPIRoute)、认证模式(tRPC使用NextAuth,公共API使用基础认证)

database-patterns.md

database-patterns.md

Dual database architecture (PostgreSQL via Prisma + ClickHouse via direct client), PostgreSQL CRUD operations, ClickHouse query patterns (queryClickhouse, queryClickhouseStream, upsertClickhouse), repository pattern for complex queries, tenant isolation with projectId filtering, when to use which database
双数据库架构(PostgreSQL通过Prisma + ClickHouse通过直接客户端)、PostgreSQL CRUD操作、ClickHouse查询模式(queryClickhouse、queryClickhouseStream、upsertClickhouse)、复杂查询的数据仓库模式、通过projectId过滤实现租户隔离、数据库选择策略

configuration.md

configuration.md

Environment variable validation with Zod, package-specific configs (web/env.mjs with t3-oss/env-nextjs, worker/env.ts, shared/env.ts), NEXT_PUBLIC_LANGFUSE_CLOUD_REGION usage, LANGFUSE_EE_LICENSE_KEY for enterprise features, best practices for env management
使用Zod验证环境变量、包特定配置(web/env.mjs使用t3-oss/env-nextjs,worker/env.ts,shared/env.ts)、NEXT_PUBLIC_LANGFUSE_CLOUD_REGION用法、企业版功能的LANGFUSE_EE_LICENSE_KEY、环境变量管理最佳实践

testing-guide.md

testing-guide.md

Integration tests (Public API with makeZodVerifiedAPICall), tRPC tests (createInnerTRPCContext, appRouter.createCaller), service-level tests (repository/service functions), worker tests (vitest with streams), test isolation principles, running tests (Jest for web, vitest for worker)

集成测试(公共API使用makeZodVerifiedAPICall)、tRPC测试(createInnerTRPCContext、appRouter.createCaller)、服务层测试(数据仓库/服务函数)、Worker测试(vitest处理流)、测试隔离原则、运行测试(Web端使用Jest,Worker端使用vitest)

Related Skills

相关技能

  • database-verification - Verify column names and schema consistency
  • skill-developer - Meta-skill for creating and managing skills

Skill Status: COMPLETE ✅ Line Count: ~540 lines Progressive Disclosure: 7 resource files ✅
  • database-verification - 验证列名和schema一致性
  • skill-developer - 创建和管理技能的元技能

技能状态:已完成 ✅ 行数:约540行 渐进式披露:7个资源文件 ✅