TypeScript는 스크립트 언어일까? 헷갈리는 기초 개념 4가지 정리
💡 이 글에서 다루는 내용
- TypeScript는 정말 스크립트 언어인가?
- TypeScript와 Node.js는 어떻게 다른가?
- JavaScript는 런타임 없이 실행되는 게 맞을까?
- 결국 JS로 변환되는데 왜 TypeScript를 쓰는가?
안녕하세요. TypeScript로 백엔드를 시작하기 전에, 의외로 많은 개발자들이 흐릿하게 알고 있는 기초 개념부터 정리하고 가려고 합니다.
저도 처음 TypeScript를 접했을 때 "이게 스크립트 언어야? 컴파일 언어야?", "Node.js랑 뭐가 다르지?" 같은 질문에 명확히 답하기 어려웠습니다. 그런데 이 부분을 명확하게 잡고 가야 이후에 어떤 런타임을 쓸지, 어떤 빌드 도구를 쓸지 같은 결정이 훨씬 쉬워집니다.
오늘은 자주 헷갈리는 4가지 질문에 차근차근 답해보겠습니다.
📌 목차
- TypeScript는 스크립트 언어인가?
- TypeScript와 Node.js는 무엇이 다른가?
- JavaScript는 런타임 없이 실행되는가?
- 결국 JS로 변환될 텐데 왜 TypeScript를 쓰는가?
1. TypeScript는 스크립트 언어인가?
결론부터 말하면 "엄밀히는 아니지만, 실용적으로는 그렇게 분류해도 큰 문제는 없다" 입니다.
스크립트 언어의 일반적인 정의
학술적으로 명확한 정의는 없지만 보통 이런 특징들을 묶어서 부릅니다.
- 인터프리터 방식으로 실행 (별도 컴파일 단계 없이 바로 실행)
- 동적 타입 (런타임에 타입 결정)
- 짧은 자동화, glue code에 자주 쓰임
- REPL이 흔함
이 기준에서 JavaScript는 전형적인 스크립트 언어입니다.
TypeScript는 두 가지가 다릅니다
① 컴파일이 필수입니다
TypeScript는 그 자체로는 절대 실행되지 않습니다. 반드시 JavaScript로 변환(트랜스파일)되어야 합니다.
.ts 파일 → [tsc / esbuild / swc] → .js 파일 → Node.js / 브라우저
C나 Java처럼 "소스 → 다른 형태 → 실행"이라는 컴파일 언어의 워크플로우를 따릅니다. 결과물이 기계어가 아니라 JavaScript일 뿐입니다.
이런 언어를 트랜스파일 언어(transpiled language) 또는 컴파일-투-JS 언어라고 부릅니다.
② 정적 타입 시스템이 핵심입니다
스크립트 언어의 큰 특징인 동적 타이핑을 TypeScript는 정면으로 거부합니다. 변수, 함수 인자, 반환 타입을 컴파일 시점에 검증합니다.
TypeScript의 존재 이유 자체가 "JavaScript에 정적 타입을 입히는 것"입니다.
그런데 왜 스크립트 언어처럼 다루는가?
- tsx, ts-node, Bun, Deno 같은 도구가 .ts 파일을 직접 실행하는 것처럼 보이게 만들어줍니다 (내부적으론 메모리에서 컴파일 후 실행)
- 결국 동작은 JavaScript로 이루어지므로 JS의 동적 특성을 그대로 물려받습니다
- 짧은 자동화 스크립트, CLI 도구도 자유롭게 작성 가능합니다
정확한 한 줄 정리
TypeScript는 JavaScript로 컴파일되는 정적 타입 언어입니다. JavaScript가 스크립트 언어이므로 TypeScript도 넓은 의미에서 스크립트 언어 계열에 속하지만, 자체적으로는 컴파일 언어의 특성을 가집니다.
2. TypeScript와 Node.js는 무엇이 다른가?
이건 헷갈리는 게 당연한 주제입니다. 한 번에 정리하면 명확해집니다.
핵심 구분
- TypeScript = 언어
- Node.js = 런타임 (실행 환경)
비유하자면 이렇습니다.
분류 언어 실행 환경
| 자동차 | 운전 규칙/설계도 | 도로 + 엔진 |
| 음식 | 레시피 | 주방 + 가스레인지 |
| 프로그래밍 | TypeScript, JavaScript | Node.js, 브라우저, Bun, Deno |
레시피만 있다고 음식이 만들어지지 않습니다. 주방이 있어야 실제 요리가 됩니다.
각각 무엇인가?
JavaScript (언어)
- ECMAScript 표준에 따른 프로그래밍 언어
- 문법과 표준 함수를 정의
TypeScript (언어)
- JavaScript에 정적 타입을 추가한 언어
- 컴파일하면 JavaScript가 됨
Node.js (런타임)
- JavaScript를 서버/터미널에서 실행할 수 있게 해주는 프로그램
- Google V8 엔진(Chrome이 쓰는 그 엔진)에 파일 시스템, 네트워크, OS 접근 같은 추가 기능을 더한 것
관계도
[TypeScript 코드]
│
│ tsc / esbuild / tsx 가 변환
▼
[JavaScript 코드]
│
│ 어디서 실행할까?
├──────────────┬──────────────┬──────────────┐
▼ ▼ ▼ ▼
[Node.js] [브라우저] [Bun] [Deno]
(서버,CLI) (웹페이지) (대안 런타임) (대안 런타임)
JavaScript는 여러 런타임에서 실행 가능합니다. Node.js는 그중 하나일 뿐입니다.
같은 코드, 다른 환경
const message: string = "Hello"
console.log(message)
이 코드가 어디서 돌아가는지에 따라 가능한 일이 달라집니다.
실행 환경 가능한 것 불가능한 것
| Node.js | 파일 읽기/쓰기, HTTP 서버, OS 접근 | DOM 조작, window, document |
| 브라우저 | DOM, window, fetch, 사용자 클릭 | 파일 시스템 직접 접근, OS 명령 |
| Bun | Node.js와 거의 호환 + 더 빠름 | (대부분 호환) |
| Cloudflare Workers | 엣지에서 HTTP 처리 | Node 전용 모듈(fs 등) |
흔한 오해 정리
❌ "Node.js로 코딩한다" ✅ 정확히는 "JavaScript/TypeScript로 코딩하고 Node.js에서 실행한다"
❌ "TypeScript는 Node.js 위에서만 돌아간다" ✅ JS로 변환된 후 어떤 런타임에서든 실행됩니다
❌ "Node.js는 언어다" ✅ Node.js는 런타임입니다
3. JavaScript는 런타임 없이 실행되는가?
이 질문에는 중요한 오해가 있습니다.
JavaScript도 런타임 없이는 절대 실행되지 않습니다.
자주 하는 착각
"브라우저에서 <script> 태그로 JS를 그냥 쓸 수 있으니까 런타임이 필요 없다"
"node script.js 치면 그냥 돌아가니까 런타임이 필요 없다"
→ 둘 다 이미 런타임 위에서 실행되고 있는 것입니다. 런타임이 안 보일 뿐입니다.
JavaScript 실행에 반드시 필요한 것
JavaScript는 인터프리터 언어라서 소스 코드를 컴퓨터가 직접 이해하지 못합니다. 누군가가 코드를 읽고 해석해서 CPU가 알아들을 수 있는 명령으로 바꿔줘야 합니다.
그 "누군가"가 바로 JavaScript 엔진이고, 엔진을 감싸 실제 환경을 제공하는 것이 런타임입니다.
[JavaScript 소스 코드]
│
▼
┌─────────────────────────────────┐
│ JavaScript 엔진 (V8, JSCore...) │ ← 코드 파싱/해석/JIT 컴파일
│ + 표준 라이브러리 │ ← console, Math, JSON 등
│ + 환경별 API (DOM 또는 fs) │ ← 환경마다 다름
└─────────────────────────────────┘
= 런타임
엔진 없이는 console.log("hi") 한 줄도 실행 안 됩니다.
"런타임 없이 실행"처럼 보였던 건 무엇이었나?
상황 실제로는
| 브라우저에서 <script> 사용 | 브라우저 자체가 런타임 (V8 + DOM API + fetch 등) |
| 터미널에서 node app.js | Node.js가 런타임 (V8 + fs, http 등) |
| 개발자 도구 콘솔에서 1+1 | 브라우저 런타임에서 실행 |
JavaScript "단독"으로 실행되는 환경은 존재하지 않습니다.
TypeScript는 한 단계 더
JavaScript:
[JS 코드] ──────────────→ [런타임] ──→ 실행
(필수)
TypeScript:
[TS 코드] ──[컴파일]──→ [JS 코드] ──→ [런타임] ──→ 실행
(필수) (필수)
tsx, ts-node, Bun, Deno 같은 도구는 컴파일 단계를 자동 처리해줘서 마치 TS를 직접 실행하는 것처럼 보일 뿐, 내부적으로는 JS로 변환 후 런타임에서 실행됩니다.
정확한 비교
항목 JavaScript TypeScript
| 컴파일/변환 단계 | 불필요 | 필수 |
| 런타임 | 필수 | 필수 |
| 단독 실행 가능? | ❌ | ❌ |
진짜 차이는 "런타임이 필요하냐"가 아니라 이렇게 표현해야 정확합니다.
JavaScript는 소스를 런타임에 바로 던질 수 있고, TypeScript는 소스를 먼저 JS로 바꾼 다음 던져야 한다.
진정한 의미의 "런타임 없이 실행되는 언어"는 C, C++, Rust, Go 같은 네이티브 컴파일 언어들입니다. 컴파일 결과물이 OS가 직접 실행할 수 있는 기계어입니다. JS와 TS는 둘 다 이 부류가 아닙니다.
4. 결국 JS로 변환될 텐데, 왜 TypeScript를 쓰는가?
가장 본질적인 질문입니다. 답은 이렇습니다.
실행은 같지만 개발 과정에서의 안전망과 생산성이 완전히 다르기 때문
비유로 시작
같은 도로를 달리는 두 자동차를 생각해보세요.
- JavaScript: 안전벨트, 차선이탈 경보, 후방 카메라가 없는 차
- TypeScript: 같은 차에 안전 보조 장치 풀세팅
목적지(실행 결과)는 같습니다. 하지만 운전하는 동안의 사고 확률, 피로도, 자신감이 완전히 다릅니다.
코드 작성 = 운전 과정입니다.
① 버그를 작성 시점에 잡는다
JavaScript 예시
function getDiscount(user) {
return user.membership.level * 0.1
}
getDiscount({ name: "Alice" }) // 💥 런타임 에러
// TypeError: Cannot read properties of undefined (reading 'level')
코드를 실행해야, 그것도 이 코드 경로를 실제로 거쳐야만 에러가 발견됩니다. 운이 나쁘면 프로덕션에서 사용자가 발견합니다.
TypeScript 예시
type User = { name: string; membership?: { level: number } }
function getDiscount(user: User) {
return user.membership.level * 0.1
// ^^^^^^^^^^^^^^^ 🔴 에디터에서 즉시 빨간 줄
// Object is possibly 'undefined'.
}
저장도 하기 전에, 빌드도 하기 전에 에디터에서 잡힙니다. 5초 만에 잡은 버그 vs 프로덕션 장애의 차이입니다.
② 자동완성과 IDE 지원
이게 사실 가장 체감되는 차이입니다.
JavaScript
function processOrder(order) {
order. // ← 에디터: "뭐가 있는지 나도 몰라요"
}
order에 뭐가 들어있는지 알려면 함수를 호출하는 모든 곳을 직접 추적해야 합니다.
TypeScript
function processOrder(order: Order) {
order. // ← 에디터가 자동 표시:
// • id: string
// • items: OrderItem[]
// • totalAmount: number
// • customer: Customer
// • createdAt: Date
}
오타도 즉시 잡힙니다(order.totalAmout → 빨간 줄). 큰 코드베이스에서 이 차이는 어마어마합니다.
③ 안전한 리팩토링
User 타입의 name 필드를 fullName으로 바꾸고 싶다고 합시다.
JavaScript의 경우
프로젝트 전체에서 .name을 검색 → 어떤 게 User의 name이고 어떤 게 다른 객체의 name인지 일일이 판단 → 빠뜨리면 런타임에서 터짐 → 두려워서 못 바꿈 → 코드가 점점 더러워집니다.
TypeScript의 경우
F2(rename) 한 번이면 끝. 못 바꾼 곳은 컴파일러가 빨간 줄로 다 알려줍니다.
이걸 "두려움 없는 리팩토링(fearless refactoring)" 이라고 부릅니다. 코드베이스가 커질수록 가치가 기하급수적으로 커집니다.
④ 살아있는 문서
function transferMoney(
fromAccount: Account,
toAccount: Account,
amount: Money,
options?: { skipValidation?: boolean; idempotencyKey?: string }
): Promise<Transaction>
함수 시그니처만 봐도 입력, 출력, 옵션이 명확합니다. JSDoc 주석 없이도 의도가 드러납니다.
6개월 뒤의 나, 또는 새로 합류한 팀원이 이 함수를 이해하는 시간이 확연히 줄어듭니다.
⑤ 팀 협업 시 계약을 강제한다
여러 명이 함께 일할 때 진짜 가치가 드러납니다.
// 백엔드 (Drizzle 스키마)
type User = { id: number; email: string; createdAt: Date }
// 프론트엔드
const user: User = await fetch('/api/users/1').then(r => r.json())
user.createdAt.getTime() // ✅ Date 메서드 사용 가능
백엔드가 createdAt을 created_at으로 바꾸면 프론트의 컴파일이 깨집니다. 두 서비스 간 계약이 코드로 강제됩니다.
핵심 통찰
타입 검사는 테스트와 비슷한 자동화된 검증 단계입니다.
"코드를 실행하지 않고도 일부 버그를 자동으로 잡아주는 무료 테스트"
JavaScript 워크플로:
코드 작성 → 실행 → 에러 발견 → 수정
↑ 여기서 처음 문제를 안다
TypeScript 워크플로:
코드 작성 → 타입 검사 → 변환 → 실행
↑ 여기서 이미 잡힘
단점도 분명히 있다
공정하게 말하자면 이런 단점이 있습니다.
단점 현실
| 빌드 단계 추가 | tsx, Bun, Deno로 거의 사라짐 |
| 타입 작성 시간 | 익숙해지면 자동완성 덕에 오히려 빨라짐 |
| 학습 곡선 | 기초는 1~2일, 고급은 몇 주 |
| 타입 정의 없는 라이브러리 | 요즘 인기 라이브러리는 거의 다 제공 |
| 타입과 런타임 불일치 | 런타임 검증은 Zod 같은 별도 도구 필요 |
언제 JavaScript가 더 나은가?
TypeScript가 만능은 아닙니다. 이런 경우엔 JavaScript가 합리적입니다.
- 20줄짜리 일회성 스크립트
- 프로토타입/해커톤
- 튜토리얼, 학습 코드
- 마이그레이션 비용이 큰 레거시 JS 프로젝트
통계로 보는 현실
이건 단순한 의견이 아닙니다.
- State of JS 2023: 신규 프로젝트의 78%가 TypeScript 사용
- npm 패키지의 90% 이상이 TypeScript 타입 정의 제공
- Google, Microsoft, Airbnb, Slack, Stripe 등이 자체 코드베이스를 JS → TS로 마이그레이션
- 신규 프레임워크/라이브러리는 거의 다 TypeScript로 작성 (Hono, Drizzle, Pothos, Next.js, NestJS 등)
이렇게 많은 회사가 비용을 들여서 TS로 바꾸는 이유는, 장기적으로 더 싸기 때문입니다.
🎯 마무리
오늘은 TypeScript의 4가지 기초 개념을 정리해봤습니다.
핵심을 다시 한 번 짚어보면 이렇습니다.
✅ TypeScript는 트랜스파일 언어입니다. 스크립트 언어 계열로 분류해도 무리는 아니지만, 자체적으로는 컴파일 언어의 특성을 가집니다.
✅ TypeScript는 언어, Node.js는 런타임입니다. 서로 다른 층위의 개념이고 보완 관계입니다.
✅ JavaScript도 런타임 없이는 실행되지 않습니다. TypeScript는 거기에 컴파일 단계가 추가될 뿐입니다.
✅ TypeScript의 본질은 "런타임에 발견될 버그의 일부를, 코드 작성 시점에 컴파일러가 미리 잡아주는 시스템"입니다.
프로젝트가 한 페이지 이상 되거나, 한 명 이상이 작업하거나, 한 달 이상 유지될 거라면 TypeScript의 이득이 비용을 거의 항상 압도합니다.
이게 사람들이 "결국 JS로 변환될 텐데"라는 사실을 알면서도 TypeScript를 쓰는 이유입니다.
다음 글에서는 이 TypeScript를 기반으로 실제 백엔드를 어떻게 구성할지, 제가 선택한 스택(Hono, Pothos, GraphQL Yoga, Drizzle, BullMQ)을 하나씩 살펴보겠습니다.
긴 글 읽어주셔서 감사합니다. 도움이 되셨다면 공감과 댓글 부탁드립니다 🙏
📚 함께 보면 좋은 글
- (다음 글) TypeScript 백엔드 스택 정하기 — Hono, Pothos, Drizzle, BullMQ
- (다음 글) TypeScript 백엔드 프로젝트 구조 — 모노레포가 답인가?
- (다음 글) TypeScript 백엔드 테스트 코드 작성하기
🏷️ 태그 #TypeScript #JavaScript #NodeJS #백엔드 #프로그래밍언어 #런타임 #트랜스파일러 #개발기초 #TS입문 #웹개발
'웹 프로그래밍 > JavaScript&TypeScript' 카테고리의 다른 글
| window.onload vs $(document).ready() vs $(window).load() (0) | 2018.10.16 |
|---|---|
| JavaScript xml 파싱 (0) | 2016.02.05 |
| JavaScript xml 사용 (0) | 2016.01.27 |
| JavaScript 함수 선언 (0) | 2016.01.04 |