Skip to main content
LibreChat is joining ClickHouse to power the open-source Agentic Data Stack 🎉 Learn more
LibreChat

코드 표준 및 관례

LibreChat 기여를 위한 코딩 표준, 작업 공간 경계 및 규칙.

작업 공간 경계 (Workspace Boundaries)

LibreChat은 모노레포(monorepo)입니다. 모든 새 코드는 올바른 워크스페이스를 대상으로 해야 합니다:

작업 공간언어측면목적
/apiJS (레거시)백엔드Express 서버 — 변경 사항 최소화
/packages/apiTypeScript백엔드새로운 백엔드 코드가 위치함 (TS 전용, /api에서 사용)
/packages/data-schemasTypeScript백엔드데이터베이스 모델/스키마 및 데이터베이스 관련 공유 로직
/packages/data-providerTypeScript공유API 타입, endpoint, 데이터 서비스 — 프론트엔드 및 백엔드에서 사용
/clientTypeScript/React프론트엔드프론트엔드 SPA
/packages/clientTypeScript프론트엔드공유 프론트엔드 유틸리티
  • /packages/api 내의 모든 신규 백엔드 코드는 반드시 TypeScript여야 합니다.
  • /api 변경 사항을 절대적으로 최소화하십시오(/packages/api를 호출하는 얇은 JS 래퍼).
  • 데이터베이스 관련 공유 로직은 /packages/data-schemas에 위치합니다.
  • 프론트엔드/백엔드 공유 API 로직(endpoints, types, data-service)은 /packages/data-provider에 위치합니다.
  • 프로젝트 루트에서 모든 컴파일된 코드를 빌드하려면 npm run build를 실행하세요.
  • API/타입 변경 후 공유 data-provider 코드를 다시 빌드하려면 다음을 실행하세요: npm run build:data-provider.

일반 지침

  • "Clean code" 원칙을 사용하세요: 함수와 모듈을 작게 유지하고, 단일 책임 원칙을 준수하며, 표현력이 풍부하고 읽기 쉬운 코드를 작성하세요.
  • 의미 있고 설명적인 변수 및 함수 이름을 사용하세요.
  • 간결함보다 코드의 가독성과 유지보수성을 우선시하십시오.
  • 일관된 코드 서식을 위해 제공된 .eslintrc.prettierrc 파일을 사용하세요.
  • 사용 가능한 경우 자동 수정을 사용하여 모든 서식 린트 오류를 수정하세요. 모든 TypeScript/ESLint 경고 및 오류는 해결되어야 합니다.

명명 및 파일 구성

  • 가능한 한 permissions.ts, capabilities.ts, 또는 service.ts와 같이 단일 단어로 된 파일 이름을 사용하세요.
  • 여러 단어가 필요한 경우, adminCapabilities.ts 대신 admin/capabilities.ts와 같이 파일의 맥락을 제공하는 단일 단어 디렉터리를 사용하는 것을 권장합니다.
  • 디렉토리가 문맥을 제공하도록 하세요. app/appConfigService.ts보다는 app/service.ts를 선호합니다.

코드 구조

  • Never-nesting: 조기 반환(early returns), 평탄한 코드 구조, 최소한의 들여쓰기를 사용하세요. 복잡한 작업은 적절한 이름의 헬퍼 함수로 분리하세요.
  • 함수형 우선: 명령형 루프 대신 순수 함수, 불변 데이터, map/filter/reduce를 사용합니다. 도메인 모델링이나 상태 캡슐화를 명확하게 개선할 수 있는 경우에만 OOP를 고려하세요.
  • **동적 임포트(dynamic imports)**는 절대적으로 필요한 경우가 아니면 사용하지 마세요.
  • 반복되는 로직을 전용 유틸리티 함수로 추출하십시오(DRY). 거의 중복되는 구현보다는 매개변수화된 헬퍼, 상수, 공유 유효성 검사기, 중앙 집중식 오류 처리 및 공유 타입을 우선적으로 사용하십시오.

반복 및 성능

  • 루핑 최소화 — 특히 메시지 배열과 같이 자주 반복되는 공유 데이터 구조에 대한 루핑을 최소화하세요. 추가적인 순회 작업이 늘어날수록 규모가 커짐에 따라 성능에 영향을 미칩니다.
  • 가능한 경우 순차적인 O(n) 작업을 단일 패스로 통합하십시오. 작업을 결합할 수 있다면 동일한 컬렉션을 두 번 반복하지 마십시오.
  • 반복 작업의 필요성을 줄이는 데이터 구조를 선택하세요 (예: Array.find/Array.includes 대신 조회를 위해 Map/Set 사용).
  • 불필요한 객체 생성을 피하고, 공간-시간 트레이드오프를 고려하세요.
  • 메모리 누수 방지: 클로저 사용에 주의하고, 리소스/이벤트 리스너를 해제하며, 순환 참조를 피하세요.

Type Safety

  • any를 절대 사용하지 마십시오. 모든 매개변수, 반환 값 및 변수에 대해 명시적 타입을 지정해야 합니다.
  • unknown 제한unknown, Record<string, unknown>, 그리고 as unknown as T 단언을 피하세요. Record<string, unknown>은 거의 항상 명시적인 타입 정의가 누락되었음을 나타냅니다.
  • 타입을 중복 정의하지 마세요 — 새로운 타입을 정의하기 전에 프로젝트(특히 packages/data-provider)에 이미 존재하는 타입인지 확인하세요. 기존 타입을 재사용하고 확장하세요.
  • 유니온 타입(union types), 제네릭(generics), 인터페이스(interfaces)를 적절하게 사용하세요.

주석 및 문서화

  • 코드가 무엇을 하는지 설명하는 인라인 주석을 달지 말고, 스스로 설명이 되는(self-documenting) 코드를 작성하세요.
  • 복잡하거나 명확하지 않은 로직, 또는 공개 API의 인텔리센스(intellisense)를 위해서만 JSDoc을 사용하세요.
  • 간략한 문서에는 한 줄 JSDoc을, 복잡한 경우에는 여러 줄 JSDoc을 사용하세요.
  • 절대적으로 필요한 경우가 아니라면 독립형 // 주석은 피하세요.

Import Order

Imports는 세 가지 섹션으로 구성됩니다 (순서대로):

  1. 패키지 임포트 — 가장 짧은 줄부터 가장 긴 줄 순서로 정렬 (react는 항상 첫 번째 임포트입니다).
  2. import type imports — 가장 긴 것부터 가장 짧은 것 순으로 정렬 (패키지 타입 우선, 그 다음 로컬 타입; 길이 정렬은 하위 그룹 간에 초기화됨).
  3. 로컬/프로젝트 임포트 — 가장 긴 것부터 짧은 것 순으로 정렬.
  • 동일한 모듈에서 가져오는 값(value) import는 가능한 한 통합하십시오.
  • 타입 임포트에는 항상 독립형 import type { ... }을 사용하세요. 값 임포트 내부에 인라인 type 키워드를 절대 사용하지 마세요 (예: import { Foo, type Bar }는 잘못된 방식입니다).

루프 기본 설정

  • 루핑을 최대한 제한하세요. 단일 패스 변환을 선호하고 동일한 데이터를 반복해서 순회하는 것을 피하세요.
  • 성능이 중요하거나 인덱스에 의존하는 작업에는 for (let i = 0; ...)를 사용하세요.
  • 단순 배열 반복을 위한 for...of.
  • for...in은 객체 속성 열거에만 사용하세요.

Node.js API Server

API 설계

  • API를 설계할 때는 RESTful 원칙을 따르세요.
  • 라우트, 컨트롤러, 서비스, 모델에는 의미 있고 설명적인 이름을 사용하세요.
  • 각 경로에 적절한 HTTP 메서드(GET, POST, PUT, DELETE)를 사용하세요.
  • 일관된 API 응답을 위해 적절한 상태 코드와 응답 구조를 사용하세요(성공 시 2xx, 클라이언트의 잘못된 요청 시 4xx, 서버 오류 시 5xx).
  • try-catch 블록을 사용하여 예외를 적절하게 포착하고 처리하세요.
  • 적절한 오류 처리를 구현하고 일관되게 알맞은 오류 응답을 반환하세요.
  • utils 디렉토리에 포함된 로깅 시스템을 사용하여 중요한 이벤트와 오류를 기록하세요.
  • requireJWTAuth 미들웨어를 사용하여 JWT 기반의 상태 비저장(stateless) 인증을 수행합니다.

파일 구조

새로운 백엔드 코드는 TypeScript로 /packages/api에 위치합니다. 레거시 /api 디렉토리는 다음 구조를 따릅니다:

경로 (Routes)

각 HTTP 요청 메서드, 사용할 미들웨어, 그리고 각 경로(route)에 대해 호출될 컨트롤러 함수를 지정합니다.

  • 각 리소스 또는 논리적 그룹별로 별도의 파일에 Express Router를 사용하여 라우트를 정의하세요.
  • 설명적인 경로 이름을 사용하고 RESTful 관례를 준수하세요.
  • 경로는 간결하게 유지하고 단일 책임에 집중하세요.
  • 모든 경로 앞에 /api 네임스페이스를 붙이세요.

Controllers

각 라우트에 대한 로직을 포함하며, 적절한 서비스 함수를 호출하고 알맞은 응답 상태 코드와 JSON 본문을 반환합니다.

  • 각 경로에 대한 요청/응답 로직을 처리하기 위해 각 경로별로 별도의 컨트롤러 파일을 생성하세요.
  • 컨트롤러 파일 이름은 PascalCase 규칙을 사용하고 파일 이름 뒤에 "Controller"를 붙이십시오(예: UserController.js).
  • 복잡한 작업은 서비스(service)나 모델(model) 파일로 위임하여 컨트롤러를 가볍게 유지하세요.

서비스

여러 컨트롤러에서 공유되는 복잡한 비즈니스 로직이나 작업을 포함합니다.

  • 서비스 파일 이름은 PascalCase 규칙을 사용하고 파일 이름 뒤에 "Service"를 붙이세요(예: AuthService.js).
  • 더 나은 재사용성을 위해 서비스를 특정 모델이나 데이터베이스에 강하게 결합하지 마십시오.
  • 각 서비스 내에서 단일 책임 원칙을 유지하세요.

모델 (Models)

데이터 엔티티와 그 관계를 나타내는 Mongoose 모델을 정의합니다.

  • 모델 파일과 관련 컬렉션에는 단수형 PascalCase 이름을 사용하세요(예: User.jsusers 컬렉션).
  • 모델에는 필요한 필드, 인덱스, 유효성 검사만 포함하세요.
  • 요청/응답 객체에 대한 직접적인 참조를 피하여 모델을 API 계층으로부터 독립적으로 유지하세요.

데이터베이스 액세스 (MongoDB 및 Mongoose)

  • MongoDB ODM으로 Mongoose(https://mongoosejs.com)를 사용하세요.
  • 각 엔티티에 대한 별도의 모델 파일을 생성하고 관심사의 명확한 분리를 보장하세요.
  • Mongoose 스키마 유효성 검사를 사용하여 데이터 무결성을 강화하세요.
  • 데이터베이스 연결을 효율적으로 관리하고 연결 누수를 방지하세요.
  • Mongoose 쿼리 빌더를 사용하여 간결하고 읽기 쉬운 데이터베이스 쿼리를 작성하세요.

React Client

일반적인 TypeScript 및 React 모범 사례

  • TypeScript 모범 사례를 사용하여 정적 타이핑과 향상된 도구 지원의 이점을 누리세요.
  • 관련 파일들을 기능 디렉토리(예: SidePanel/Memories/) 내에 함께 그룹화하세요.
  • PascalCase 규칙을 사용하여 컴포넌트 이름을 지정하세요.
  • 구성 요소의 목적을 정확하게 반영하는 간결하고 설명적인 이름을 사용하세요.
  • 적절한 경우 복잡한 컴포넌트를 더 작고 재사용 가능한 컴포넌트로 분할하세요.
  • 컴포넌트 내의 렌더링 로직을 최소한으로 유지하세요.
  • 재사용 가능한 부분을 별도의 함수나 훅으로 추출하세요.
  • TypeScript 타입 또는 인터페이스를 사용하여 prop 타입 정의를 적용하세요.
  • 적절한 경우 폼 유효성 검사를 사용하세요(폼 유효성 검사 및 제출에는 React Hook Form을 사용합니다).

현지화

  • 사용자에게 표시되는 모든 텍스트는 useLocalize() 훅을 사용하여 현지화해야 합니다.
  • client/src/locales/en/translation.json의 영어 키만 업데이트하세요(다른 언어는 외부에서 자동으로 처리됩니다).
  • com_ui_, com_assistants_ 등과 같은 의미론적 로컬라이제이션 키 접두사를 사용하세요.
  • 새로운 로컬라이제이션 키에 대해서는 항상 의미 있는 대체 텍스트(fallback text)를 제공하세요.

데이터 서비스

  • client/src/data-provider/[Feature]/queries.ts에 데이터 제공자 훅을 생성하세요.
  • client/src/data-provider/[Feature]/index.ts에서 모든 훅을 내보내세요.
  • client/src/data-provider/index.ts 메인 파일에 기능 내보내기(exports)를 추가하세요.
  • 모든 API 상호작용에는 React Query(@tanstack/react-query)를 사용하세요.
  • 변이(mutation) 시 적절한 쿼리 무효화(query invalidation)를 구현하세요.
  • packages/data-provider/src/keys.ts에 QueryKeys와 MutationKeys를 추가하세요.

공유 API 통합을 추가할 때 다음을 업데이트하세요:

  • packages/data-provider/src/api-endpoints.ts (엔드포인트)
  • packages/data-provider/src/data-service.ts (데이터 서비스 함수)
  • packages/data-provider/src/types/queries.ts (TypeScript 타입)

성능

  • 대규모 환경에서 메모리와 속도 효율성을 우선시하십시오.
  • 대규모 데이터셋을 위해 적절한 커서 기반 페이지네이션(cursor pagination)을 구현하세요.
  • 적절한 의존성 배열(dependency arrays)을 사용하여 불필요한 리렌더링을 방지하세요.
  • React Query의 캐싱 및 백그라운드 재요청(refetching) 기능을 활용하세요.

테스트 및 문서화

  • Jest를 사용하여 모든 중요하고 복잡한 기능에 대한 단위 테스트를 작성하세요.
  • Supertest를 사용하여 모든 API endpoint에 대한 통합 테스트를 작성하세요.
  • Playwright를 사용하여 모든 클라이언트 측 기능에 대한 엔드투엔드(end-to-end) 테스트를 작성하세요.
  • 테스트의 목적을 명확하게 표현할 수 있도록 서술적인 테스트 케이스 및 함수 이름을 사용하세요.
  • 해당 워크스페이스 디렉토리에서 테스트를 실행하세요: cd api && npx jest <pattern>, cd packages/api && npx jest <pattern> 등.
  • UI/데이터 흐름에 대한 로딩, 성공 및 오류 상태를 다룹니다.
  • 프론트엔드 테스트에서 컴포넌트를 렌더링하려면 test/layout-test-utils를 사용하세요.
  • 모의 객체(mock)보다는 실제 로직을 우선하십시오. 외부 HTTP API, 속도 제한이 있는 서비스, 비결정적 시스템 호출과 같이 로컬에서 제어할 수 없는 경우에만 모의 객체를 사용하십시오.
  • 기본 구현을 대체하지 않고 호출 여부를 확인해야 할 때는 스파이(spies)를 사용하세요.
  • MongoDB 기반 테스트를 위해 mongodb-memory-server를 사용하여 쿼리 및 스키마 유효성 검사가 실제 데이터베이스 동작을 수행하도록 하세요.

이 가이드는 어떤가요?