npm — Node.js 생태계의 심장, 패키지 매니저의 시작
프론트엔드 개발 · 프론트엔드 도구 시리즈 #1
전 세계 200만 개 이상의 패키지를 보유한 세계 최대 소프트웨어 레지스트리. npm이 무엇인지, 어떻게 동작하는지, 그리고 실무에서 어떻게 잘 쓰는지 완전히 이해합니다.
1. npm이란 무엇인가
npm(Node Package Manager) 은 세 가지를 동시에 의미합니다.
레지스트리(Registry): 전 세계 개발자가 공개한 200만 개 이상의 JavaScript 패키지가 저장된 중앙 저장소입니다. https://registry.npmjs.org에 호스팅됩니다.
CLI 도구: 레지스트리에서 패키지를 설치·업데이트·제거하고 로컬 프로젝트를 관리하는 명령줄 도구입니다.
웹사이트: https://npmjs.com — 패키지 검색, 문서, 다운로드 통계를 제공합니다.
npm은 2009년 Isaac Z. Schlueter가 만들었고, 2020년 GitHub(Microsoft)이 npm Inc.를 인수했습니다. Node.js를 설치하면 npm이 자동으로 함께 설치됩니다.
node --version # v20.11.0
npm --version # 10.2.4
2. package.json 완전 해부
package.json은 프로젝트의 신분증이자 설계도입니다. 프로젝트 메타데이터, 의존성, 스크립트를 정의합니다.
{
"name": "my-app",
"version": "1.0.0",
"description": "내 첫 번째 Node.js 앱",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"vite": "^5.0.0",
"@vitejs/plugin-react": "^4.0.0",
"typescript": "^5.3.0"
},
"peerDependencies": {
"react": ">=17.0.0"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
},
"main": "dist/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js",
"types": "./dist/index.d.ts"
}
},
"files": ["dist", "README.md"],
"keywords": ["react", "component", "ui"],
"author": "홍길동 <hong@example.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/hong/my-app.git"
}
}
</hong@example.com>
핵심 필드 설명
name과 version: 패키지를 npm 레지스트리에 퍼블리시할 때 고유 식별자 역할을 합니다. private: true이면 실수로 퍼블리시되지 않습니다.
exports: Node.js 12+에서 지원하는 패키지 진입점 정의. main과 module보다 우선하며, 조건부 내보내기(ESM/CJS)를 지원합니다.
engines: 이 패키지가 동작하는 Node.js, npm 버전 범위를 명시합니다. CI/CD에서 버전 불일치를 사전에 방지합니다.
3. package-lock.json과 의존성 잠금
package-lock.json은 npm이 자동으로 생성·관리하는 파일입니다. 팀 전체와 CI 환경에서 정확히 동일한 의존성 트리를 재현하기 위한 잠금 파일(lockfile) 입니다.
{
"name": "my-app",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": { "react": "^18.2.0" }
},
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-...",
"dependencies": {
"loose-envify": "^1.1.0"
}
}
}
}
왜 lockfile을 커밋해야 하는가
package.json: "react": "^18.2.0" → 18.x.x 어떤 버전이든 가능
package-lock.json: "react": "18.2.0" → 반드시 이 정확한 버전
팀원 A가 npm install → 18.2.0 설치
팀원 B가 npm install (lockfile 없음) → 18.3.1 설치 → "내 컴에선 되는데?"
lockfile은 반드시 git에 커밋해야 합니다. .gitignore에 package-lock.json을 추가하는 것은 잘못된 관행입니다.
npm ci — CI 환경을 위한 설치
# 개발 환경: package.json 기준 설치 후 lockfile 업데이트
npm install
# CI 환경: lockfile 기준 정확한 설치 (lockfile 수정 안 함)
npm ci
npm ci는 node_modules를 지우고 처음부터 재설치하므로 항상 깨끗한 상태를 보장합니다.
4. 핵심 CLI 명령어
설치
# 패키지 설치 (dependencies)
npm install react
npm install react react-dom axios
# 개발 의존성 설치
npm install --save-dev typescript eslint prettier
npm install -D vitest # 단축 형식
# 전역 설치
npm install -g serve create-next-app
# 정확한 버전 설치
npm install react@18.0.0
npm install react@next # next 태그
# 특정 버전 범위
npm install "react@>=17.0.0 <19.0.0"
제거 및 업데이트
# 제거
npm uninstall axios
npm uninstall -D eslint # devDependencies에서 제거
# 업데이트
npm update # 모든 패키지를 범위 내 최신으로
npm update react # 특정 패키지만
npm install react@latest # 최신 버전으로
# 오래된 패키지 확인
npm outdated
Package Current Wanted Latest Location
react 18.0.0 18.2.0 18.2.0 node_modules/react
typescript 4.9.0 4.9.5 5.3.0 node_modules/typescript
정보 조회
# 설치된 패키지 목록
npm list
npm list --depth=0 # 직접 의존성만
# 패키지 상세 정보
npm info react
npm info react version # 최신 버전만
npm info react versions # 모든 버전 목록
# 패키지 위치
npm root # node_modules 경로
npm bin # .bin 경로 (전역: npm bin -g)
실행
# package.json scripts 실행
npm run dev
npm run build
npm test # test는 run 생략 가능
npm start # start도 run 생략 가능
# npx — 설치 없이 패키지 실행
npx create-next-app@latest my-app
npx prettier --write .
npx tsc --init
5. 의존성 종류와 버전 범위
의존성 종류
{
"dependencies": {
"react": "^18.2.0"
},
"devDependencies": {
"typescript": "^5.3.0"
},
"peerDependencies": {
"react": ">=17.0.0"
},
"optionalDependencies": {
"fsevents": "^2.3.3"
}
}
dependencies: 프로덕션 런타임에 필요한 패키지. 앱 배포 시 포함됩니다.
devDependencies: 개발·빌드·테스트에만 필요한 패키지. NODE_ENV=production에서 npm install --production 실행 시 제외됩니다.
peerDependencies: 라이브러리 작성 시 사용. "이 패키지를 쓰려면 React 17 이상이 호스트 프로젝트에 있어야 합니다"라고 선언합니다. npm v7+에서는 peer dependencies를 자동 설치합니다.
optionalDependencies: 설치 실패해도 오류가 발생하지 않습니다. 플랫폼 특화 바이너리(macOS의 fsevents 등)에 사용합니다.
시맨틱 버저닝 (SemVer)
1.2.3
│ │ └── PATCH: 버그 수정, 하위 호환
│ └──── MINOR: 새 기능 추가, 하위 호환
└────── MAJOR: 파괴적 변경
버전 범위 연산자
"react": "18.2.0" 정확히 이 버전만
"react": ">18.0.0" 18.0.0 초과
"react": ">=18.0.0" 18.0.0 이상
"react": "^18.2.0" 18.2.0 이상 19.0.0 미만 (MAJOR 고정)
"react": "~18.2.0" 18.2.0 이상 18.3.0 미만 (MINOR 고정)
"react": "*" 모든 버전 (권장하지 않음)
"react": "latest" 최신 버전 태그
^(caret) 이 기본값입니다. MAJOR가 0인 경우(^0.2.3)는 MINOR도 고정(0.2.x만 허용)합니다. 이는 0.x.y 버전의 패키지는 마이너 변경도 파괴적일 수 있기 때문입니다.
6. npm 스크립트 마스터하기
package.json의 scripts 필드는 단순한 명령어 단축키를 넘어 강력한 빌드 파이프라인을 구성할 수 있습니다.
기본 패턴
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"test:ci": "vitest run --coverage",
"lint": "eslint src --ext .ts,.tsx",
"lint:fix": "eslint src --ext .ts,.tsx --fix",
"format": "prettier --write .",
"typecheck": "tsc --noEmit"
}
}
pre/post 훅
스크립트 이름 앞에 pre나 post를 붙이면 자동으로 전후에 실행됩니다.
{
"scripts": {
"preinstall": "node check-node-version.js",
"prebuild": "npm run typecheck && npm run lint",
"build": "vite build",
"postbuild": "node scripts/copy-assets.js",
"pretest": "npm run lint",
"test": "vitest run",
"posttest": "npm run build"
}
}
병렬/직렬 실행
# 직렬 실행 (앞이 실패하면 중단)
"build": "tsc && vite build && npm run copy"
# 병렬 실행 (모두 동시에)
"dev": "vite & tsc --watch"
# npm-run-all: 크로스 플랫폼 병렬/직렬
npm install -D npm-run-all
{
"scripts": {
"dev": "run-p dev:*",
"dev:vite": "vite",
"dev:ts": "tsc --watch",
"check": "run-s lint typecheck test",
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"test": "vitest run"
}
}
환경 변수와 cross-env
npm install -D cross-env
{
"scripts": {
"build": "cross-env NODE_ENV=production vite build",
"build:dev": "cross-env NODE_ENV=development vite build",
"test": "cross-env NODE_ENV=test vitest run"
}
}
cross-env는 Windows에서도 NODE_ENV=production처럼 환경 변수를 설정할 수 있게 해줍니다.
7. npm workspaces — 모노레포 지원
npm v7부터 공식 지원하는 모노레포 기능입니다. 여러 패키지를 단일 저장소에서 관리합니다.
// root package.json
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
]
}
my-monorepo/
├── package.json (root)
├── package-lock.json (루트에서 통합 관리)
├── node_modules/ (공유 의존성)
├── packages/
│ ├── ui/
│ │ └── package.json { "name": "@myapp/ui" }
│ └── utils/
│ └── package.json { "name": "@myapp/utils" }
└── apps/
└── web/
└── package.json { "name": "@myapp/web" }
# 루트에서 특정 workspace에 명령 실행
npm run build -w @myapp/ui
npm install axios -w @myapp/web
# 모든 workspace에 명령 실행
npm run test --workspaces
npm run build --workspaces --if-present # 스크립트 없으면 건너뜀
# workspace 간 의존성 (로컬 패키지 참조)
npm install @myapp/ui -w @myapp/web
8. 보안 — npm audit
# 취약점 검사
npm audit
# 자동 수정 (가능한 경우)
npm audit fix
# 파괴적 업데이트 허용 (SemVer 범위 무시)
npm audit fix --force
# JSON 출력 (CI 파이프라인에서 활용)
npm audit --json
found 3 vulnerabilities (1 low, 1 moderate, 1 high)
run `npm audit fix` to fix them, or `npm audit` for details
.npmrc — npm 설정 파일
# .npmrc (프로젝트 루트)
# 레지스트리 변경 (사설 레지스트리)
registry=https://registry.npmjs.org/
# 스코프별 레지스트리
@mycompany:registry=https://npm.mycompany.com/
# 인증 토큰
//npm.mycompany.com/:_authToken=${NPM_TOKEN}
# 설치 옵션
save-exact=true # ^ 없이 정확한 버전으로 저장
engine-strict=true # engines 필드 불일치 시 오류
# legacy-peer-deps: v7+ peer deps 자동 설치 비활성화
legacy-peer-deps=true
9. 성능과 한계 — yarn, pnpm과의 관계
npm은 세 가지 측면에서 yarn, pnpm 대비 약점이 있습니다.
디스크 사용량
npm과 yarn은 프로젝트마다 node_modules를 독립적으로 복사합니다.
project-a/node_modules/lodash (3.8MB)
project-b/node_modules/lodash (3.8MB) ← 동일한 파일이 중복!
project-c/node_modules/lodash (3.8MB)
pnpm은 전역 저장소에 한 번만 저장하고 심볼릭 링크를 사용합니다. 디스크 공간을 60~80% 절약합니다.
설치 속도
npm install: 빠름
yarn install: 더 빠름 (병렬 다운로드, 캐시 최적화)
pnpm install: 가장 빠름 (글로벌 캐시 + 하드링크)
Phantom Dependencies 문제
npm의 평탄화(hoisting) 전략으로 인해 package.json에 선언하지 않은 패키지를 require할 수 있습니다. pnpm의 엄격한 의존성 격리가 이를 방지합니다.
언제 npm을 선택하는가
- Node.js 기본 내장으로 별도 설치 불필요
- 간단한 프로젝트, 스크립트, 개인 프로젝트
- 팀이 새로운 도구 학습을 피하고 싶을 때
- GitHub Actions 등 CI 환경의 기본 도구
10. 결론
npm은 JavaScript 생태계의 기반 인프라입니다. yarn이나 pnpm이 성능 면에서 앞서 있어도, npm이 설정하는 레지스트리, package.json, SemVer 규약은 모든 패키지 매니저의 공통 언어입니다.
npm을 깊이 이해하는 것은 yarn이나 pnpm을 이해하기 위한 선행 조건이기도 합니다.
선택 기준 권장
| 단순 프로젝트, 입문 | npm |
| 대규모 팀, 빠른 CI | yarn 또는 pnpm |
| 모노레포, 디스크 절약 | pnpm |
| Next.js 공식 권장 | npm 또는 pnpm |
한 줄 요약: npm은 JavaScript의 물류 시스템입니다. 느릴 수 있지만, 어디서나 존재합니다.
댓글