Menu

npm 기초: install, init, update와 의존성 관리

npm이 실제로 어떻게 동작하는지 — 패키지 설치부터 init, dev 의존성, 업데이트, 그리고 node_modules와 lockfile의 작동 원리까지 정리했습니다.

npm이 대체 뭔가요?

npm은 한 이름 아래 세 가지 역할을 동시에 합니다. 먼저 레지스트리(registry) 입니다. npmjs.com에 올라가 있는 거대한 자바스크립트 패키지 공개 저장소죠. 두 번째로 커맨드라인 도구입니다. Node.js에 기본으로 포함되어 있어서 패키지를 설치하고 관리할 때 씁니다. 마지막으로 프로젝트가 어떤 패키지를 필요로 하는지 기술하는 명세(specification) 입니다. 바로 package.json 포맷이죠.

예를 들어 npm install express를 실행하면, CLI가 레지스트리에 요청을 보내 express와 그 의존 패키지들을 전부 내려받고, node_modules 폴더에 파일을 풀어놓은 뒤, 패키지 이름과 버전을 package.json에 기록합니다. npm 사용법의 핵심은 사실상 이 한 사이클이 전부입니다.

Node.js를 이미 설치했다면 npm도 같이 깔려 있습니다. 한번 확인해 볼까요:

node --version
npm --version

버전이 둘 다 출력되면 준비 완료입니다.

프로젝트 시작하기: npm init

모든 npm 프로젝트에는 package.json 파일이 필요합니다. 이 파일은 일종의 명세서로, 프로젝트 이름과 버전, 스크립트, 의존성 정보를 담고 있죠. 가장 빠르게 만드는 방법은 npm init -y 명령어인데, 기본값을 그대로 받아들여 파일을 생성해 줍니다:

mkdir my-app
cd my-app
npm init -y

다음과 같이 출력됩니다:

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

-y 옵션을 빼면 npm이 각 항목을 하나씩 물어보면서 대화형으로 진행해 줍니다. 어느 쪽을 선택하든 결과물은 package.json — 앞으로 모든 게 여기에 붙게 되는 핵심 파일이죠. 이 파일의 각 필드는 다음 페이지에서 자세히 다루겠습니다.

npm 패키지 설치하기

package.json이 준비됐다면, 이제 npm install 명령어(줄여서 npm i)로 패키지를 설치할 수 있습니다:

npm install lodash

세 가지 일이 일어납니다:

  1. npm이 lodash와 그 의존성 패키지들을 node_modules/ 폴더에 내려받습니다.
  2. package.jsondependencies 항목에 "lodash": "^4.17.21"(또는 그 시점의 최신 버전)을 추가합니다.
  3. package-lock.json 파일을 만들어 의존성 트리에 포함된 모든 패키지의 정확한 버전을 기록합니다.

이제 바로 사용할 수 있습니다:

index.js
Output
Click Run to see the output here.

require 호출(ESM 프로젝트라면 import)은 node_modules 안을 뒤져서 해당 패키지를 찾아냅니다. 경로를 직접 적을 필요는 없고, Node의 모듈 리졸버가 알아서 처리해 줍니다.

dependencies와 devDependencies의 차이

프로덕션 환경에서 앱이 돌아갈 때 모든 패키지가 필요한 건 아닙니다. 테스트 프레임워크, 린터, 번들러 같은 도구는 개발할 때만 쓰이죠. 이런 패키지는 --save-dev(줄여서 -D) 옵션으로 devDependencies에 설치합니다:

npm install --save-dev jest
npm install -D eslint prettier

이 경우에는 dependencies가 아니라 devDependencies에 등록됩니다:

{
  "dependencies": {
    "lodash": "^4.17.21"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "eslint": "^8.57.0",
    "prettier": "^3.2.5"
  }
}

프로덕션 서버라면 npm install --omit=dev로 dev 섹션을 통째로 건너뛸 수 있어 설치 용량도 줄고 속도도 빨라집니다. 이 구분을 제대로 잡아두는 게 생각보다 중요한데요, 예를 들어 webpack을 실수로 dependencies에 넣어두면 프로덕션 배포마다 불필요한 용량이 따라붙게 됩니다.

한 번에 모든 패키지 설치하기

이미 package.json이 있는 저장소를 클론했다면 패키지를 하나씩 나열할 필요가 없습니다. 다음 명령만 실행하면 됩니다:

npm install

아무 인자 없이 npm install만 실행하면 npm이 package.json을 읽어서(이때 package-lock.json에 명시된 정확한 버전을 그대로 존중합니다) 의존성 트리 전체를 node_modules에 설치해 줍니다. 프로젝트를 새로 받아왔을 때 가장 먼저 돌려야 하는 명령이죠.

이런 이유로 node_modules는 반드시 .gitignore에 넣어야 합니다. 어차피 lockfile만 있으면 똑같이 재현되고, 용량도 엄청나며, 누가 npm install을 돌릴 때마다 계속 바뀌거든요. 저장소에는 package.jsonpackage-lock.json만 커밋하고, node_modules는 각자 알아서 다시 만들게 두면 됩니다.

npm 패키지 업데이트하기

업데이트가 필요한 패키지는 npm outdated로 확인할 수 있습니다:

npm outdated

Current, Wanted, Latest 세 개의 컬럼이 있는 표가 보일 겁니다. _Wanted_는 package.json에 명시된 범위 안에서 설치할 수 있는 가장 최신 버전이에요. 예를 들어 ^4.17.21이라면 5.0.0 미만의 최신 버전을 의미합니다. _Latest_는 npm에 배포된 최신 버전인데, 아직 허용하지 않은 메이저 업데이트일 수도 있어요.

허용된 범위 안에서 패키지를 업데이트하려면 다음 명령어를 사용하세요:

npm update

메이저 버전까지 포함해 진짜 최신 버전으로 올리고 싶다면, @latest를 붙여서 패키지를 다시 설치하면 됩니다:

npm install lodash@latest

메이저 버전이 올라간다는 건 곧 기존 코드가 깨질 수 있다는 신호입니다. 버전 번호가 대놓고 경고해주는 셈이죠. 메이저 버전을 넘길 때는 반드시 체인지로그부터 확인하세요.

패키지 제거하기

설치의 반대 과정도 똑같이 간단합니다:

npm uninstall lodash

이 명령을 실행하면 node_modules에서 해당 패키지가 제거되고 package.json의 항목도 함께 삭제됩니다. 개발 의존성이었다면 -D 플래그를 붙여주세요. npm이 알아서 판단해주긴 하지만, 명시적으로 적어두면 스크립트에서 예상치 못한 동작을 피할 수 있습니다.

전역 설치 vs 로컬 설치

대부분의 경우 로컬 설치가 정답입니다. 프로젝트의 node_modules 안에 버전을 고정해서 쓰는 방식이죠. 예외가 있다면 어디서든 실행하고 싶은 커맨드라인 도구 정도입니다:

npm install -g typescript
npm install -g http-server

전역 설치를 하면 해당 도구가 시스템 전역 경로에 설치되고, bin 항목이 PATH에 등록되기 때문에 어느 디렉터리에서든 tschttp-server 같은 명령을 실행할 수 있습니다. 다만 전역 설치는 프로젝트 단위로 관리되지 않기 때문에, 장비마다 버전이 어긋나기 쉽다는 단점이 있습니다.

일회성으로 명령어를 실행하고 싶을 때는 npm에 기본 포함된 npx를 쓰는 게 훨씬 깔끔한 대안입니다:

npx create-react-app my-app
npx prettier --write .

npx는 패키지를 전역으로 설치하지 않고도 바로 실행할 수 있게 해줍니다. 필요할 때마다 가져와서 실행하고 끝내는 방식이죠. 한두 번 쓰고 말 도구라면 전역으로 설치해두는 것보다 훨씬 깔끔합니다.

자주 쓰는 명령어 모음

실제로 매일같이 쓰게 되는 명령어들은 다음과 같습니다:

npm init -y                     # package.json 생성
npm install                     # package.json에 있는 모든 것을 설치
npm install <pkg>               # 런타임 의존성 추가
npm install -D <pkg>            # 개발 의존성 추가
npm install -g <pkg>            # CLI 도구를 전역으로 설치
npm uninstall <pkg>             # 의존성 제거
npm outdated                    # 오래된 패키지 확인
npm update                      # 허용된 범위 내에서 업데이트
npm install <pkg>@latest        # 최신 버전으로 업그레이드
npm run <script>                # package.json의 스크립트 실행
npx <pkg>                       # 설치 없이 패키지 실행

npm의 큰 그림은 여기까지다. 패키지 배포나 workspaces, 스코프 패키지 같은 주제는 실제로 필요해질 때 찾아봐도 늦지 않다.

node_modules 안에는 뭐가 들어 있을까

마지막으로 개념 하나만 정리하자. node_modules는 프로젝트가 의존하는 모든 패키지와, 그 패키지들이 또 의존하는 패키지까지 전부 담아 두는 거의 평탄한(flat) 폴더다. 패키지 하나 설치했는데 백 개가 딸려 들어오는 경우도 흔하다. 그게 정상이다. npm은 가능한 한 중복을 제거해 주기 때문에, 두 패키지가 같은 버전의 lodash를 쓴다면 복사본은 하나만 남긴다.

락파일(package-lock.json)에는 이 모든 패키지의 정확한 해석된 버전이 기록된다. 빌드가 재현 가능해지는 이유가 바로 여기에 있다. 같은 락파일로 npm install을 돌리면, 서로 다른 개발자가 몇 달 간격을 두고 설치하더라도 바이트 단위로 동일한 트리를 얻는다.

node_modules는 생성된 결과물로만 취급하자. 안에 있는 파일을 직접 고치는 건 절대 금물이다. 누군가 다시 설치하는 순간 수정한 내용은 그대로 사라진다.

다음 주제: package.json

package.json은 npm이 뒤에서 끊임없이 읽고 또 갱신하는 파일이다. scripts, main, type, 버전 범위, engines 같은 필드들을 제대로 이해하고 나면, npm은 더 이상 블랙박스가 아니라 내 손안에서 다룰 수 있는 도구가 된다. 다음 장에서 이어서 살펴보자.

자주 묻는 질문

npm이 뭔가요?

npm은 Node.js의 기본 패키지 매니저입니다. Node를 설치하면 함께 깔리고, 방대한 공개 레지스트리에서 JavaScript 패키지를 받아올 수 있는 CLI 도구를 제공합니다. 예를 들어 npm install lodash를 실행하면 npm이 레지스트리에서 lodash를 내려받아 node_modules에 넣고, 그 사실을 package.json에 기록합니다.

dependencies랑 devDependencies는 뭐가 다른가요?

dependenciesexpressreact처럼 실제 운영 환경에서 앱이 돌아가는 데 필요한 패키지입니다. 반면 devDependencies는 테스트 러너, 번들러, 린터처럼 개발하거나 빌드할 때만 필요한 것들이죠. 후자는 npm install --save-dev <pkg> (줄여서 -D)로 설치합니다. 배포 환경에서는 npm install --omit=dev로 devDependencies를 빼고 설치할 수 있습니다.

node_modules를 git에 커밋해야 하나요?

아니요, 커밋하면 안 됩니다. node_modules는 금방 수백 MB로 불어나고, 어차피 package.jsonpackage-lock.json만 있으면 똑같이 재현할 수 있습니다. .gitignore에 추가하고 lockfile만 커밋하세요. 그래야 다른 사람이 저장소를 클론한 뒤 npm install만 돌려도 완전히 동일한 의존성 트리를 얻을 수 있습니다.

npm 전역 설치와 로컬 설치는 어떻게 다른가요?

로컬 설치(npm install <pkg>)는 해당 프로젝트의 node_modules 안에 패키지를 설치하고 package.json에 기록합니다. 전역 설치(npm install -g <pkg>)는 시스템 전체에 설치해서 어디서나 쓸 수 있게 하는데, 주로 CLI 도구를 깔 때 사용합니다. 프로젝트 의존성은 버전이 프로젝트별로 고정되도록 로컬 설치를 쓰는 게 좋습니다.

Coddy로 코딩 배우기

시작하기