패키지 매니저부터 모노레포까지 (01) - npm이란 무엇인가
npm을 단순한 설치 명령어가 아니라, registry와 package.json, 의존성 관리 흐름까지 포함한 기본 축으로 다시 정리합니다.
패키지 매니저 이야기를 시작하려면, 먼저 npm이 정확히 무엇인지부터 다시 잡아야 합니다. install 명령어 하나로 이해하면 이후 흐름이 자꾸 얕아집니다.
들어가며
npm은 너무 익숙해서 오히려 대충 알고 넘어가기 쉬운 도구입니다. 프런트엔드 프로젝트를 시작할 때도, 라이브러리를 설치할 때도, 배포 전에 의존성을 맞출 때도 거의 자동처럼 npm install을 입력하게 됩니다.
문제는 여기서 npm을 "설치 명령어" 정도로만 이해하고 멈추기 쉽다는 점입니다. 하지만 시리즈의 출발점으로 놓고 보면, npm은 단순한 CLI가 아니라 Node 생태계가 패키지를 배포하고 설치하고 해석하는 기본 축에 더 가깝습니다.
이 말을 조금 더 쉽게 풀어보면, npm은 온라인 쇼핑몰의 검색창 하나만 가리키는 이름이 아닙니다. 상품을 올려두는 창고, 상품 설명서, 주문 내역, 배송 규칙이 함께 묶인 시스템에 더 가깝습니다. 우리가 평소에는 검색창에만 손을 대기 때문에 npm도 명령어 몇 개로만 보일 뿐입니다.
물론 실제 기술 개념은 비유보다 더 구체적입니다. npm을 이해하려면 아래 네 가지를 함께 봐야 합니다.
- 터미널에서 실행하는 CLI
- 패키지를 올리고 내려받는 registry
- 프로젝트의 약속을 적는
package.json - 의존성 트리를 실제로 설치하는 흐름
이 네 가지가 함께 붙어 있어야 이후의 Yarn, pnpm, workspace, monorepo 이야기도 자연스럽게 이어집니다.
npm은 명령어 하나가 아니라, 패키지를 선언하고 설치하고 재현하는 기본 흐름 전체에 더 가깝습니다.
npm을 무엇으로 봐야 하는가
npm을 하나의 프로그램 이름으로만 보면 자꾸 설명이 어긋납니다. 실제로는 적어도 아래 네 가지를 함께 보는 편이 더 정확합니다.
1. npm은 CLI입니다
CLI는 Command Line Interface의 줄임말입니다. 화면의 버튼을 누르는 대신, 터미널에 명령어를 입력해서 도구를 다루는 방식이라고 생각하면 됩니다.
가장 눈에 익은 npm의 모습도 바로 이 CLI입니다. npm install, npm run dev, npm publish처럼 개발자가 직접 실행하는 명령어들이 여기에 해당합니다.
공식 문서도 npm을 Node JavaScript 플랫폼의 패키지 매니저라고 설명합니다. 즉 npm CLI는 패키지를 설치하고, 실행하고, 배포하는 입구 역할을 합니다.
하지만 CLI만으로는 npm을 다 설명할 수 없습니다. 왜냐하면 이 명령어들이 결국 어떤 저장소와 어떤 메타데이터를 기준으로 움직이기 때문입니다.
2. npm은 registry와 연결되어 있습니다
registry는 패키지 저장소라고 생각하면 됩니다. 누군가 배포한 패키지와 그 버전 정보가 모여 있는 중앙 목록이라고 보면 이해하기 쉽습니다.
여기서 말하는 registry는, 우리가 설치할 패키지를 찾을 때 기준이 되는 주소록에 가깝습니다.
npm은 기본적으로 public registry와 통신합니다. 우리가 npm install react를 실행하면, 단순히 로컬 폴더를 복사하는 것이 아니라 registry에서 패키지 메타데이터와 tarball 정보를 확인한 뒤 필요한 버전을 가져옵니다.
이때 중요한 점은 npm이 단순한 다운로드 도구가 아니라는 점입니다. 패키지 이름, 버전 범위, dist-tag 같은 규칙도 registry와 함께 이해해야 합니다. 그래서 latest 태그가 가리키는 버전이 무엇인지, 특정 범위 안에서 어떤 버전이 선택되는지도 모두 이 생태계 안에서 결정됩니다.
즉 npm install react는 "react 파일 좀 가져와 줘"에 가깝지 않습니다. 더 정확하게는 "registry에서 react라는 이름을 가진 패키지의 현재 조건에 맞는 버전을 찾아서, 이 프로젝트에 연결해 달라"에 가깝습니다.
3. npm은 package.json을 중심으로 움직입니다
package.json은 프로젝트 설명 파일처럼 보이지만, 실제로는 프로젝트가 어떤 패키지인지와 어떤 의존성을 요구하는지를 선언하는 기준점에 가깝습니다. 사람 기준으로 비유하면 자기소개서이자 준비물 목록에 가깝습니다.
패키지 이름, 버전, 스크립트, 의존성 범위, workspace 설정 같은 것들이 모두 여기에 들어갑니다. 즉 npm은 이 파일을 읽고 "이 프로젝트가 어떤 약속 위에 서 있는가"를 판단합니다.
이 파일이 중요한 이유는 npm이 추측으로 움직이지 않기 때문입니다. 지금 이 프로젝트가 앱인지 라이브러리인지, 실행에 무엇이 필요한지, 어떤 명령어로 개발 서버를 켜야 하는지 같은 정보가 모두 여기서 시작됩니다.
4. npm은 설치 결과를 lockfile로 고정합니다
lockfile이라는 말도 처음 들으면 다소 추상적입니다. 이름 그대로 생각하면 "이번 설치 결과를 잠가 두는 기록" 정도로 이해할 수 있습니다.
여기서 lockfile은, 말 그대로 설치 결과를 고정해 두는 기록에 가깝습니다.
package.json이 허용 범위를 적는 문서라면, package-lock.json은 실제로 무엇이 설치되었는지를 기록하는 문서입니다. 팀원이 같은 프로젝트를 설치할 때 결과가 크게 흔들리지 않도록 돕는 역할도 여기서 나옵니다.
이 네 가지를 함께 보면 npm은 단순한 설치 명령어가 아니라, 패키지를 선언하고 배포하고 재현하는 표준 흐름에 가깝다는 점이 보입니다.
package.json은 왜 중요한가
처음에는 package.json을 프로젝트 설정 파일 정도로 생각하기 쉽습니다. 하지만 실제로는 "이 프로젝트가 무엇이고 무엇을 필요로 하는가"를 외부에 설명하는 문서에 더 가깝습니다.
예를 들어 팀원이 새로 프로젝트를 받아서 설치한다고 가정해 보겠습니다. 그 사람이 코드를 하나하나 읽기 전에 먼저 보는 것은 대개 package.json입니다. 어떤 이름의 프로젝트인지, 어떤 도구를 쓰는지, 무엇을 설치해야 하는지, 어떤 명령어로 실행하는지가 여기 모여 있기 때문입니다.
예를 들어 아주 단순한 파일도 이미 여러 약속을 담고 있습니다.
{
"name": "my-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build"
},
"dependencies": {
"react": "^19.0.0",
"next": "^16.0.0"
}
}여기서 dependencies는 "이 패키지가 실행되기 위해 필요한 것"을 선언합니다. scripts는 팀이 공통으로 실행하는 진입점을 맞추는 역할을 합니다. name과 version은 특히 패키지를 배포할 때 중요한 식별자가 됩니다.
개발을 막 시작했을 때는 dependencies와 devDependencies, scripts, version 같은 필드가 전부 비슷해 보일 수 있습니다. 하지만 실제로는 역할이 분명히 나뉘어 있습니다.
dependencies: 실행에 필요한 라이브러리 목록scripts: 팀이 반복해서 실행하는 명령어 별칭name,version: 이 패키지가 누구인지 식별하는 정보
즉 package.json은 단순한 설정 모음이 아니라, 프로젝트가 생태계 안에서 자신을 설명하는 문서입니다.
공식 package.json 문서도 name과 version을 패키지 식별의 핵심으로 다룹니다. publish하지 않는 앱이라면 체감이 덜할 수 있지만, 라이브러리나 workspace로 가기 시작하면 이 정보는 단순한 장식이 아니라 구조의 일부가 됩니다.
결국 package.json은 npm이 읽는 프로젝트 소개서이면서, 동시에 생태계가 이 프로젝트를 해석하는 기준입니다. 이후 Yarn이나 pnpm을 쓰더라도 대부분은 이 파일을 중심으로 돌아갑니다. 그래서 npm 이야기를 하면서 package.json을 빼고 설명하면 구조가 자꾸 반만 보이게 됩니다.
install은 실제로 무엇을 하는가
npm install은 겉으로 보기에는 "패키지 하나를 설치하는 명령어"처럼 보입니다. 하지만 실제로는 훨씬 많은 일이 한꺼번에 일어납니다.
패키지 이름만 보고 끝나지 않습니다
예를 들어 아래 명령어를 실행한다고 가정해 보겠습니다.
npm install react이 명령은 단순히 react 하나만 내려받는 일이 아닙니다. npm은 먼저 현재 프로젝트의 package.json과 lockfile을 확인하고, 어떤 의존성 범위를 추가해야 하는지 판단합니다. 그다음 registry를 조회해 맞는 버전을 고르고, 필요한 하위 의존성까지 포함한 트리를 계산합니다.
겉으로는 한 줄 명령어지만, 내부에서는 "무엇을 설치할지 찾고, 어떤 버전이 맞는지 고르고, 함께 따라오는 패키지까지 계산하고, 결과를 기록하는 일"이 묶여 있는 셈입니다.
설치는 항상 트리 계산과 함께 갑니다
패키지는 거의 혼자 오지 않습니다. 어떤 패키지를 설치하면 그 패키지가 요구하는 다른 패키지들도 함께 따라옵니다. 그래서 설치는 파일 다운로드라기보다, 의존성 그래프를 만족하는 트리를 만드는 과정에 가깝습니다.
여기서 말하는 의존성은 "이 패키지가 동작하려면 다른 무엇이 더 필요한가"를 뜻합니다. 앱이든 라이브러리든 완전히 혼자 돌아가는 경우는 많지 않기 때문에, 설치는 늘 연결 관계를 다루는 작업이 됩니다.
이 과정에서 peer dependency, 중복 제거, hoisting 전략 같은 개념도 등장합니다. 나중에 Yarn과 pnpm을 이해할 때 중요한 이유가 여기에 있습니다. 모두 같은 install 명령을 제공하는 것처럼 보여도, 내부에서 트리를 어떻게 해석하고 배치하느냐는 꽤 다를 수 있기 때문입니다.
지금 단계에서 이 용어들을 모두 외울 필요는 없습니다. 중요한 점은 하나입니다. install은 단순 복사가 아니라, 규칙에 맞게 의존성 구조를 계산하는 작업이라는 점입니다.
lockfile은 설치 결과를 고정합니다
공식 문서 기준으로 package-lock.json은 npm이 node_modules 트리나 package.json을 변경하는 작업을 할 때 자동으로 생성됩니다. 이 파일은 "이번 설치에서 실제로 어떤 결과가 나왔는가"를 기록합니다.
여기서 중요한 점은 역할 분리입니다.
package.json은 허용 가능한 범위를 말합니다.package-lock.json은 실제로 선택된 결과를 기록합니다.
그래서 팀 프로젝트에서는 보통 둘 다 함께 관리해야 합니다. 하나만 보면 의도가 보이고, 다른 하나를 보면 실제 결과가 보입니다.
이 차이는 생각보다 큽니다. package.json만 있으면 "대충 이 범위 안에서 설치해도 된다"까지만 보이고, package-lock.json까지 있어야 "실제로는 이 버전들이 설치됐다"를 알 수 있습니다. 그래서 협업에서는 lockfile을 같이 관리하는 관습이 자연스럽게 자리 잡았습니다.
왜 npm이 기본 축인가
시리즈를 시작하면서 굳이 npm부터 잡는 이유도 여기에 있습니다. 지금도 많은 도구가 npm을 완전히 벗어나기보다, npm이 만든 기본 계약 위에서 다른 선택을 제시하는 방식으로 움직이기 때문입니다.
예를 들어 Yarn이나 pnpm을 써도 대개 package.json을 읽고, registry와 호환되는 패키지 이름 체계를 사용하고, lockfile과 workspace 같은 개념을 각자 다른 방식으로 발전시킵니다. 즉 경쟁 도구가 생겼다고 해서 npm의 기준선이 사라진 것은 아닙니다.
오히려 반대로 보는 편이 맞습니다.
- npm은 가장 넓게 통하는 기본 문법을 제공합니다.
- 다른 패키지 매니저는 그 위에서 설치 전략이나 엄격성을 다르게 가져갑니다.
- 그래서 npm을 이해하면 다른 도구를 볼 때도 비교 기준이 생깁니다.
예전의 저는 npm을 그냥 오래된 기본값 정도로 생각한 적이 있습니다. 그런데 조금 더 들여다보고 나니, npm은 "기본이라서 의미 없는 선택지"가 아니라, Node 생태계에서 가장 먼저 이해해야 할 기준축에 더 가까웠습니다.
이건 언어를 배울 때 기본 문법을 먼저 잡는 것과 비슷합니다. 모두가 같은 문장을 쓰는 것은 아니지만, 기본 문법을 알아야 다른 표현이 왜 달라졌는지를 이해할 수 있습니다. npm도 마찬가지입니다. npm을 이해해야 Yarn이 무엇을 바꾸려 했는지, pnpm이 어디를 더 엄격하게 보려 했는지 비교가 됩니다.
이 기준축을 먼저 잡아두면 이후 글도 훨씬 읽기 쉬워집니다. 왜 Yarn이 속도와 lockfile 경험을 밀어붙였는지, 왜 pnpm이 node_modules를 유지하면서도 더 엄격한 구조를 택했는지, 왜 workspace가 결국 monorepo 이야기로 이어지는지도 모두 npm의 기본 모델과 비교하면서 봐야 이해가 됩니다.
다음 글 예고
이번 글은 기준선을 세우는 글이었습니다. npm이 단순한 설치 명령어가 아니라 CLI, registry, package.json, lockfile, install 흐름을 함께 묶는 기본 축이라는 점만 잡아도 이후 시리즈가 훨씬 덜 헷갈립니다.
다음 글에서는 여기서 한 걸음 더 나가 보려고 합니다. npm이 기본 축이라면, 왜 사람들은 거기서 멈추지 않고 Yarn과 pnpm 같은 다른 패키지 매니저를 만들었을까요. 다음 편에서는 바로 그 문제의식부터 정리해 보겠습니다.
참고
- npm: npm CLI가 무엇을 담당하는지 가장 넓게 설명하는 공식 문서입니다.
- Registry: npm이 기본으로 어떤 registry를 사용하고 패키지를 어떻게 조회하는지 설명합니다.
- package.json:
name,version,scripts,dependencies,workspaces같은 핵심 필드를 정리한 문서입니다. - npm install: 설치가 실제로 어떤 규칙과 우선순위로 동작하는지 설명합니다.
- package-lock.json: lockfile이 왜 필요한지와 어떤 정보를 기록하는지 설명합니다.