타입스크립트는 자바스크립트의 단점을 보완하기 위해 등장한 정적 타입 기반 언어입니다. 자바스크립트와 호환되며 강력한 타입 시스템을 제공해 대규모 애플리케이션 개발 시 안정성과 가독성을 높여주는 장점이 있습니다. 하지만 타입스크립트를 도입한다고 해서 모든 문제가 자동으로 해결되는 것은 아닙니다. 실무에서는 코드 품질뿐 아니라, 프로젝트의 구조 자체가 효율성과 유지보수성에 큰 영향을 미치기 때문입니다.
본 글에서는 타입스크립트 프로젝트를 어떻게 구조화하고 설계하면 실무에서 효과적일지에 대해 구체적으로 안내합니다. 프런트엔드, 백엔드, 전체 스택을 막론하고 공통적으로 적용 가능한 전략을 중심으로 소개합니다.
모듈화 된 디렉터리 구조 설계
타입스크립트 프로젝트를 체계적으로 설계하려면 먼저 디렉터리 구조를 어떻게 구성할 것인지부터 결정해야 합니다. 특히 프로젝트가 커질수록 모듈화 된 구조를 갖추는 것이 중요합니다. 모듈화란 기능, 역할, 책임 단위로 폴더와 파일을 나누는 전략입니다.
가장 많이 사용되는 기본 구조는 다음과 같습니다:
src/
├── common/ # 공통 모듈 (에러처리, 응답 포맷 등)
├── config/ # 환경설정 관련
├── controllers/ # 라우터 처리 담당
├── services/ # 비즈니스 로직
├── models/ # 데이터 모델 및 DB 처리
├── interfaces/ # 인터페이스 및 타입 정의
├── dtos/ # 요청 및 응답 스펙 정의
├── utils/ # 유틸 함수 모음
├── middlewares/ # 미들웨어 로직
└── main.ts # 엔트리 포인트
이 구조는 백엔드(Node.js, NestJS)든 프론트엔드(React, Next.js)든 동일하게 적용할 수 있으며, 규모에 따라 하위 폴더를 기능별로 나누는 것도 좋습니다. 예를 들어 `src/features/users` 폴더 아래에 `user.controller.ts`, `user.service.ts`, `user.dto.ts`를 함께 배치하면 하나의 기능을 중심으로 모든 요소가 모이므로 유지보수와 테스트가 훨씬 수월합니다.
또한 폴더 및 파일 네이밍 컨벤션도 중요합니다. 일반적으로는 camelCase 또는 kebab-case 중 하나로 일관성을 유지하고, 각 모듈의 `index.ts` 파일을 통해 내부 요소를 export 하여 외부 접근 시 경로를 단순화할 수 있습니다.
TSConfig와 절대경로 설정
프로젝트가 커질수록 상대 경로를 계속 사용하는 것은 유지보수에 불리합니다. 예를 들어 `import { getUser } from '../../../../services/users'`와 같은 코드는 한눈에 어떤 모듈인지 파악하기 어렵고, 디렉터리 구조가 바뀌면 수많은 경로를 일일이 수정해야 합니다.
이런 문제를 해결하기 위해 `tsconfig.json`의 `paths` 옵션을 활용한 절대 경로 설정이 필수입니다. 다음과 같은 설정 예제를 살펴보겠습니다:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@controllers/*": ["controllers/*"],
"@services/*": ["services/*"],
"@models/*": ["models/*"],
"@utils/*": ["utils/*"],
"@config/*": ["config/*"]
}
}
}
이렇게 설정하면 `import { UserService } from '@services/users'` 와 같이 간단하고 직관적인 경로를 사용할 수 있습니다. 실제로 많은 기업들이 `@` prefix를 붙여 루트 디렉토리를 지정하고 있습니다. 절대 경로 설정은 개발 생산성을 높이는 것뿐 아니라 코드리딩, 협업, 리팩토링을 훨씬 쉽게 만들어 줍니다.
또한 `tsconfig`에서 반드시 챙겨야 할 옵션은 다음과 같습니다:
"strict": true
– 타입 안정성을 극대화"esModuleInterop": true
– 모듈 호환성 확보"resolveJsonModule": true
– JSON 파일 import 가능"noImplicitAny": true
– 암시적 any 타입 방지
이 외에도 컴파일 결과물을 관리하기 위한 `outDir`, `rootDir`, `include`, `exclude` 옵션은 CI/CD 연동 시에도 큰 도움이 됩니다.
예를 들어 `src`는 개발용, `dist`는 빌드용으로 분리하고 Git에선 `dist`를 무시하도록 `.gitignore`에 명시해야 합니다.
의존성 관리 및 공통 모듈 분리 전략
구조 설계의 완성은 의존성 설계에 달려 있습니다. 각 기능 모듈이 서로 과도하게 의존하지 않도록 느슨한 결합을 지향해야 하고, 공통 코드나 중복되는 코드는 반드시 분리하여 재사용할 수 있도록 해야 합니다.
예를 들어 다음과 같은 디렉토리 구분은 많은 기업에서 공통적으로 사용됩니다:
src/utils
: 로깅, 날짜 포맷, 암호화 등 유틸 함수src/constants
: 앱 전역에서 사용하는 상수값src/types
: 글로벌 인터페이스 및 타입src/common
: 에러 처리, API 응답 포맷 처리
이렇게 분리하면 특정 모듈에만 종속되지 않으므로 코드 재사용성과 유지보수성이 크게 향상됩니다. 또한 의존성 주입(Dependency Injection) 패턴을 적극 활용하면 각 모듈이 직접 서로를 참조하지 않고도 유기적으로 연결될 수 있습니다.
예를 들어 NestJS 같은 프레임워크는 DI 패턴을 내장하고 있으며, 모듈 단위로 서비스를 주입받아 테스트 용이성과 확장성을 확보할 수 있습니다. 이와 함께 단위 테스트를 고려한 설계를 하려면 각 서비스의 인터페이스를 먼저 정의하고 구현체는 별도로 나누는 방식이 좋습니다.
마지막으로 코드 스타일 통일도 중요한 설계 요소입니다. 아래 도구들은 팀의 코드 품질과 협업 생산성을 크게 높여줍니다:
ESLint
: 문법 및 코드 규칙 검사Prettier
: 코드 포맷 자동 정리Husky
+lint-staged
: 커밋 전 자동 검사Commitlint
: 커밋 메시지 형식 강제
이런 도구를 설정해두면 실수로 잘못된 코드가 커밋되는 것을 방지하고, 누구나 동일한 구조와 컨벤션을 따르게 되어 유지보수 비용이 줄어듭니다. 장기적으로 봤을 때 구조 설계 못지않게 중요한 요소입니다.
요약하자면, 코드 구조를 잘 설계하는 것은 단지 폴더를 나누는 것을 넘어서서 전체 개발 흐름과 협업 문화에 영향을 주는 중요한 작업입니다. 초기부터 잘 설계된 구조는 팀의 생산성과 프로젝트의 성공을 좌우할 수 있습니다.
타입스크립트의 강점을 극대화하려면 단순히 문법을 익히는 데 그치지 않고, 구조 설계 단계부터 체계적으로 접근해야 합니다. 디렉터리 구성, tsconfig 설정, 의존성 관리, 도구 세팅까지 세심하게 설계하면 개발 생산성과 유지보수 효율성이 크게 향상됩니다. 지금 바로 여러분의 프로젝트에 이 구조를 도입해 보세요!