모노레포로 타입스크립트 풀스택 개발 간소화하기
Ethan Miller
Product Engineer · Leapcell

소개
현대 웹 개발의 활기찬 환경에서 강력한 풀스택 애플리케이션을 구축하는 것은 종종 프론트엔드 프레임워크와 백엔드 서비스 간의 복잡한 상호 작용을 포함합니다. 프로젝트가 확장됨에 따라 React 애플리케이션과 NestJS API의 분리된 코드베이스를 관리하는 것은 빠르게 상당한 오버헤드가 될 수 있습니다. 파편화는 중복된 구성, 일관성 없는 종속성, 비효율적인 빌드 프로세스로 이어집니다. 이것이 바로 모노레포 접근 방식이 빛나는 지점으로, 이러한 문제를 정면으로 해결하는 통합 개발 경험을 제공합니다. 관련된 프로젝트를 단일 리포지토리 내에 통합함으로써 코드 공유, 빌드 최적화 및 전반적인 개발 생산성에서 강력한 이점을 얻을 수 있습니다. 이 글은 Nx와 Turborepo와 같은 도구가 React + NestJS 타입스크립트 프로젝트를 위한 이러한 간소화된 워크플로를 어떻게 지원하는지 자세히 살펴봅니다.
모노레포 이점 이해하기
Nx와 Turborepo의 구체적인 내용에 들어가기 전에 모노레포 맥락에서 그들의 힘의 기반이 되는 핵심 개념에 대한 기초적인 이해를 확립해 보겠습니다.
모노레포(Monorepo): 여러 개의 독립적인 프로젝트(예: 프론트엔드, 백엔드, 공유 라이브러리)가 단일 버전 제어 리포지토리 내에 저장되는 개발 전략입니다. 이는 각 프로젝트가 자체 별도 리포지토리에 있는 "폴리의포(polyrepo)" 접근 방식과 대조됩니다.
워크스페이스(Workspace): Nx와 Turborepo의 맥락에서 워크스페이스는 모노레포의 루트 디렉토리를 참조하며, 모든 개별 애플리케이션과 라이브러리를 포함합니다.
애플리케이션(App): 모노레포 내의 배포 가능한 단위로, React 프론트엔드 또는 NestJS 백엔드 API 등이 있습니다.
라이브러리(Lib): 모노레포 내의 여러 애플리케이션 또는 다른 라이브러리에서 공유될 수 있는 재사용 가능한 코드 블록입니다. 이는 일관성을 강제하고 중복을 줄이는 초석입니다. 예로는 공유 UI 구성 요소, 데이터 모델, 유틸리티 함수 또는 API 인터페이스가 있습니다.
태스크 러너/빌드 시스템: Nx 및 Turborepo와 같은 도구는 지능형 태스크 러너 역할을 합니다. 모노레포의 프로젝트 간 종속성을 이해하고 빌드, 테스트, 린트 및 서빙과 같은 다양한 개발 작업을 최적화할 수 있습니다.
종속성 그래프(Dependency Graph): 이러한 도구가 프로젝트(앱 및 라이브러리) 간의 관계를 표현하는 중요한 개념입니다. 이 그래프를 통해 변경 후 어떤 프로젝트를 다시 빌드해야 하는지 결정하고 올바른 순서로 작업을 실행할 수 있습니다.
분산 캐싱(Distributed Caching): 개발을 더욱 가속화하기 위해 이러한 도구는 캐싱을 활용합니다. 종속성이 변경되지 않았다면, 빌드 결과물을 캐시(로컬 또는 원격)에서 재사용하여 후속 빌드 및 테스트를 크게 단축할 수 있습니다.
이제 Nx와 Turborepo가 React + NestJS 타입스크립트 프로젝트를 간소화하기 위해 이러한 개념을 어떻게 구현하는지 살펴보겠습니다.
타입스크립트 풀스택 개발을 위한 Nx
Nx는 모노레포 철학을 포용하는 강력하고 확장 가능한 빌드 시스템입니다. 대규모 엔터프라이즈급 애플리케이션 관리를 위한 포괄적인 기능 세트를 제공합니다. 강력한 의견과 코드 생성 기능을 제공하여 프로젝트를 빠르게 시작하고 일관성을 유지하는 데 탁월합니다.
Nx 워크스페이스 설정
새로운 Nx 워크스페이스를 생성하고 React 및 NestJS 애플리케이션을 추가하는 것부터 시작하겠습니다.
npx create-nx-workspace@latest fullstack-monorepo --preset=react-standalone --appName=frontend --style=css
이 명령은 frontend
라는 React 애플리케이션과 함께 fullstack-monorepo
라는 새 Nx 워크스페이스를 생성합니다. 이제 NestJS 애플리케이션(종종 API라고 함)을 추가해 보겠습니다.
cd fullstack-monorepo npm install -D @nx/nest nx g @nx/nest:application backend --dryRun=false
이제 워크스페이스 구조는 다음과 같을 것입니다.
fullstack-monorepo/
├── apps/
│ ├── backend/ # NestJS 애플리케이션
│ └── frontend/ # React 애플리케이션
├── libs/ # 공유 라이브러리
├── nx.json # Nx 구성
├── package.json
└── tsconfig.base.json
라이브러리 생성 및 공유
모노레포의 진정한 힘은 코드를 공유하기 시작할 때 나옵니다. 데이터 모델 또는 API 유형을 위한 공유 라이브러리를 만들어 보겠습니다.
nx g @nx/js:library shared-data --dryRun=false
이것은 libs/shared-data
를 생성합니다. libs/shared-data/src/lib/shared-data.ts
에서 다음과 같은 인터페이스를 정의할 수 있습니다.
// libs/shared-data/src/lib/shared-data.ts export interface User { id: string; name: string; email: string; } export function getUserGreeting(user: User): string { return `Hello, ${user.name}!`; }
이제 frontend
와 backend
모두 이 라이브러리를 사용할 수 있습니다.
apps/backend/src/app/app.service.ts
:
// apps/backend/src/app/app.service.ts import { Injectable } from '@nestjs/common'; import { User, getUserGreeting } from '@fullstack-monorepo/shared-data'; // 라이브러리에서 가져오기 @Injectable() export class AppService { getData(): { message: string } { const user: User = { id: '1', name: 'Alice', email: 'alice@example.com' }; return { message: getUserGreeting(user) }; } }
그리고 apps/frontend/src/app/app.tsx
:
// apps/frontend/src/app/app.tsx import React, { useEffect, useState } from 'react'; import { User, getUserGreeting } from '@fullstack-monorepo/shared-data'; // 라이브러리에서 가져오기 export function App() { const [greeting, setGreeting] = useState(''); useEffect(() => { // 프론트엔드에서의 예시 사용 const user: User = { id: '2', name: 'Bob', email: 'bob@example.com' }; setGreeting(getUserGreeting(user)); // 예시: NestJS 백엔드에서 데이터 가져오기 fetch('/api') .then((res) => res.json()) .then((data) => console.log('Backend response:', data.message)); }, []); return ( <> <h1>Welcome, Frontend!</h1> <p>{greeting}</p> </> ); } export default App;
@fullstack-monorepo/shared-data
별칭을 사용한 가져오기 경로에 주목하십시오. Nx는 이러한 타입스크립트 경로 매핑을 tsconfig.base.json
에 자동으로 구성하여 원활한 가져오기를 가능하게 합니다.
Nx로 작업 실행
Nx는 모노레포 전체의 작업을 실행하는 강력한 명령어를 제공합니다.
- 두 애플리케이션을 동시에 실행:
nx run-many --target=serve --projects=frontend,backend --parallel
- 영향받은 모든 프로젝트 빌드:
이 명령은 변경 사항을 지능적으로 감지하고 직접적으로 또는 간접적으로 영향을 받은 프로젝트만 다시 빌드합니다.nx affected:build
- 테스트 실행:
nx test backend
Nx의 종속성 그래프 분석은 작업이 효율적으로 실행되도록 보장하며, 캐싱 메커니즘(로컬 및 원격)은 빌드 시간을 크게 단축합니다.
속도와 단순성을 위한 Turborepo
Vercel에 인수된 Turborepo는 속도와 사용 편의성에 중점을 두어 차별화됩니다. 기본적으로 JavaScript 및 타입스크립트 모노레포를 위한 고성능 빌드 시스템으로, 빠른 빌드, 스마트 캐싱 및 효율적인 작업 오케스트레이션을 우선시합니다. Nx가 생성기와 플러그인을 갖춘 보다 의견이 있는 전체 제품군 경험을 제공하는 반면, Turborepo는 더 무관심하며 순전히 가속화된 빌드에 중점을 둡니다.
Turborepo 워크스페이스 설정
Turborepo를 시연하기 위해 새로 시작하겠습니다.
npx create-turbo-app
프롬프트를 따르고 지금은 minimal
설정을 선택합니다. 이렇게 하면 기본 워크스페이스 구조가 생성됩니다.
turbo-monorepo/
├── apps/
├── packages/ # Nx의 libs와 유사
├── turborepo.json
├── package.json
React 및 NestJS 애플리케이션을 수동으로 생성합니다.
React의 경우:
apps/frontend
에서 표준 React 프로젝트(예: Vite 또는 Create React App 사용)를 만듭니다.
frontend
의 package.json
:
{ "name": "frontend", "version": "1.0.0", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview" }, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.0.3", "typescript": "^5.0.2", "vite": "^4.4.5" } }
NestJS의 경우:
apps/backend
에서 NestJS 프로젝트를 생성합니다.
backend
의 package.json
:
{ "name": "backend", "version": "1.0.0", "private": true, "scripts": { "build": "nest build", "start": "nest start", "start:dev": "nest start --watch" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "jest": "^29.5.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" } }
공유 패키지(라이브러리)
Turborepo에서 공유 코드는 packages
디렉토리에 위치합니다. packages/shared-data
를 만들어 보겠습니다.
packages/shared-data/package.json
:
{ "name": "shared-data", "version": "1.0.0", "main": "./src/index.ts", "types": "./src/index.ts", "private": true, "scripts": { "build": "echo \"No build needed for shared-data, direct TS usage.\"" } }
packages/shared-data/src/index.ts
:
// packages/shared-data/src/index.ts export interface User { id: string; name: string; email: string; } export function getUserGreeting(user: User): string { return `Hello, ${user.name}!`; }
애플리케이션에서 이를 사용하기 위해 해당 package.json
파일에 shared-data
를 종속성으로 추가합니다.
apps/backend/package.json
:
{ // ... 기타 필드 "dependencies": { "@nestjs/common": "^10.0.0", // ... "shared-data": "workspace:*" // npm/yarn/pnpm에게 로컬 패키지를 연결하도록 알립니다. } }
apps/frontend/package.json
:
{ // ... 기타 필드 "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "shared-data": "workspace:*" } }
그런 다음 모노레포의 루트에서 npm install
을 실행하여 이러한 패키지를 연결합니다.
Turborepo 태스크 구성
Turborepo의 핵심은 워크스페이스 루트의 turbo.json
파일입니다. 각 패키지에 대한 태스크(build
, dev
, test
)와 해당 종속성을 정의합니다.
turbo.json
:
{ "$schema": "https://turbo.build/schema.json", "pipe": { "build": { "cache": true, "dependsOn": ["^build"], "outputs": ["dist/**"] }, "dev": { "cache": false, "persistent": true }, "test": { "cache": true, "outputs": ["coverage/**"] } } }
cache: true
:build
및test
태스크에 대한 캐싱을 활성화합니다.dependsOn: ["^build"]
:^
는 모든 종속된 패키지의build
태스크에 대한 종속성을 나타냅니다. 따라서apps/frontend
의build
태스크는packages/shared-data
의build
태스크에 종속됩니다(빌드 태스크가 있다면, 또는shared-data
가 준비되었음을 확실히 함).outputs
: 캐시에서 어떤 파일을 캐시하고 복원할지를 지정합니다.persistent: true
:dev
와 같이 지속적으로 실행되는 태스크는 영구적으로 표시되어야 합니다.
Turborepo로 태스크 실행
이제 간단한 명령으로 모노레포 전체의 태스크를 실행할 수 있습니다.
- 모든 프로젝트 빌드:
turbo build
- 모든
dev
태스크를 동시에 실행:
이렇게 하면 React 프론트엔드와 NestJS 백엔드가 모두 개발 모드로 시작됩니다.turbo dev
- 특정 프로젝트에 대한 테스트 실행:
turbo run test --filter=backend
Turborepo는 자동으로 태스크를 병렬화하고 캐시를 활용하여 실제로 실행해야 하는 작업만 실행합니다.
Nx와 Turborepo 중 선택하기
Nx와 Turborepo 모두 모노레포 관리에 훌륭한 선택이지만, 약간 다른 요구 사항을 충족합니다.
- Nx: 더 의견이 많고 기능이 풍부한 경험을 제공합니다. 코드 생성기, 다양한 프레임워크(React, Angular, NestJS 등)를 위한 플러그인, 내장 프로젝트 그래프 시각화를 제공합니다. 구조화된 접근 방식과 표준화된 프로젝트 설정을 활용하는 대규모 팀과 기업에 선호되는 경우가 많습니다. "모노레포 운영 체제"를 원한다면 Nx가 강력한 경쟁자입니다.
- Turborepo: 빠르고 유연하며 비관계적인 빌드 시스템이 되는 데 중점을 둡니다. 기존 모노레포 또는 개발자가 프로젝트 구성에 대한 더 많은 제어를 선호하는 새로운 모노레포를 가속화하는 데 탁월합니다. 주요 관심사가 속도이고 기존 도구 체인을 방해하지 않는 것이라면 Turborepo는 훌륭한 선택입니다.
풀스택 React + NestJS 타입스크립트 프로젝트의 경우 두 가지 도구 모두 상당한 이점을 제공할 수 있습니다. Nx의 생성기는 모범 사례를 내장하여 더 빠르게 시작하는 데 도움이 될 수 있으며, Turborepo는 기존 빌드 스크립트를 최소한의 오버헤드와 최대한의 속도로 통합하는 것을 선호하는 경우 매력적일 수 있습니다.
결론
Nx 또는 Turborepo와 같은 도구로 구동되는 모노레포 구조 내에서 풀스택 React 및 NestJS 타입스크립트 프로젝트를 관리하는 것은 개발 효율성과 유지 관리성에서 상당한 도약을 나타냅니다. 라이브러리를 통한 쉬운 코드 공유, 지능형 캐싱 및 병렬 처리를 통한 빌드 프로세스 최적화, 통합 개발 경험 제공을 통해 이러한 도구는 개발자가 복잡한 애플리케이션에 접근하는 방식을 변화시킵니다. Nx 또는 Turborepo로 모노레포 전략을 채택하는 것은 단순히 코드를 구성하는 것이 아니라, 빠르게 변화하는 풀스택 프로젝트에서 더 빠르고, 더 일관성 있게, 더 큰 자신감으로 구축하는 것을 의미합니다.