코드 표준 및 관례
LibreChat 기여를 위한 코딩 표준, 작업 공간 경계 및 규칙.
작업 공간 경계 (Workspace Boundaries)
LibreChat은 모노레포(monorepo)입니다. 모든 새 코드는 올바른 워크스페이스를 대상으로 해야 합니다:
| 작업 공간 | 언어 | 측면 | 목적 |
|---|---|---|---|
/api | JS (레거시) | 백엔드 | Express 서버 — 변경 사항 최소화 |
/packages/api | TypeScript | 백엔드 | 새로운 백엔드 코드가 위치함 (TS 전용, /api에서 사용) |
/packages/data-schemas | TypeScript | 백엔드 | 데이터베이스 모델/스키마 및 데이터베이스 관련 공유 로직 |
/packages/data-provider | TypeScript | 공유 | API 타입, endpoint, 데이터 서비스 — 프론트엔드 및 백엔드에서 사용 |
/client | TypeScript/React | 프론트엔드 | 프론트엔드 SPA |
/packages/client | TypeScript | 프론트엔드 | 공유 프론트엔드 유틸리티 |
/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는 세 가지 섹션으로 구성됩니다 (순서대로):
- 패키지 임포트 — 가장 짧은 줄부터 가장 긴 줄 순서로 정렬 (
react는 항상 첫 번째 임포트입니다). import typeimports — 가장 긴 것부터 가장 짧은 것 순으로 정렬 (패키지 타입 우선, 그 다음 로컬 타입; 길이 정렬은 하위 그룹 간에 초기화됨).- 로컬/프로젝트 임포트 — 가장 긴 것부터 짧은 것 순으로 정렬.
- 동일한 모듈에서 가져오는 값(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.js및users컬렉션). - 모델에는 필요한 필드, 인덱스, 유효성 검사만 포함하세요.
- 요청/응답 객체에 대한 직접적인 참조를 피하여 모델을 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를 사용하여 쿼리 및 스키마 유효성 검사가 실제 데이터베이스 동작을 수행하도록 하세요.
이 가이드는 어떤가요?