본문 바로가기

npm과 패키지 관리

package.json과 의존성 범위를 이해하고 npm으로 패키지 설치·업데이트·삭제·스크립트 실행을 관리합니다.

현대 웹 개발은 HTML/CSS/JavaScript만으로 끝나지 않습니다.

React, Express.js 같은 프레임워크와 Axios, Lodash, date-fns 같은 유틸리티를 함께 사용하므로 외부 라이브러리 관리가 필수입니다.

여기서 등장하는 것이 바로 패키지 관리자(Package Manager)입니다.

패키지 관리자는 프로젝트가 의존하는 라이브러리(패키지)들을 설치, 업데이트, 삭제하며 효율적으로 관리해주는 도구입니다.

Node.js 생태계에서 가장 널리 사용되는 패키지 관리자는 바로 npm (Node Package Manager)입니다.

이번 장에서는 npm의 기본 개념과 주요 기능, 그리고 프로젝트 패키지를 효율적으로 관리하는 방법을 정리합니다.

npm 이해는 모던 JavaScript 개발에서 필수입니다.


패키지와 패키지 관리자란?

패키지 (Package)

패키지는 특정 기능을 수행하기 위해 미리 작성되고 모듈화된 코드, 즉 라이브러리나 프레임워크를 의미합니다.

웹 개발에서는 react, express, axios, lodash 등이 모두 패키지의 예시입니다.

이 패키지들은 다른 개발자들이 만들어 공개한 오픈소스이며, 우리는 이를 가져다 사용함으로써 개발 시간을 단축하고 안정성을 높일 수 있습니다.

패키지 관리자 (Package Manager)

패키지 관리자는 이러한 패키지들을 검색, 설치, 업데이트, 제거하는 일련의 과정을 자동화하고 효율적으로 관리해주는 도구입니다.

또한, 특정 패키지가 동작하기 위해 필요한 다른 패키지(의존성, Dependency)들을 자동으로 설치해주고 관리하는 역할도 수행합니다.

Node.js 환경에서는 npm이 가장 대표적인 패키지 관리자이며, Yarn도 npm과 유사한 기능을 제공하는 인기 있는 대안입니다.


npm이란?

npm (Node Package Manager)은 Node.js의 기본 패키지 관리자이자, 세계에서 가장 큰 소프트웨어 레지스트리(저장소) 중 하나입니다.

수백만 개의 오픈소스 JavaScript 패키지들이 npm 레지스트리에 등록되어 있으며, 누구나 이를 검색하고 사용할 수 있습니다.

npm의 주요 기능

  • 패키지 설치: npm install <package-name> 명령어를 통해 필요한 패키지를 쉽게 설치할 수 있습니다.
  • 패키지 제거: npm uninstall <package-name> 명령어로 패키지를 제거합니다.
  • 패키지 업데이트: npm update <package-name> 명령어로 허용된 버전 범위 안에서 패키지를 업데이트합니다. 레지스트리의 가장 최신 메이저 버전까지 무조건 올리는 명령은 아닙니다.
  • 의존성 관리: 프로젝트의 package.json 파일을 기반으로 필요한 모든 의존성 패키지를 자동으로 설치합니다.
  • 스크립트 실행: package.json에 정의된 커스텀 스크립트(예: npm start, npm test, npm run build)를 실행할 수 있습니다.
  • 패키지 발행: 자신이 만든 패키지를 npm 레지스트리에 배포하여 다른 개발자들이 사용할 수 있도록 합니다.

package.json 파일의 이해

먼저 요구 범위를 적는 manifest와 해결한 조합을 기록하는 lock을 구별한 뒤 설치 명령을 고릅니다.

의존성은 실행에 쓰는 패키지와 개발 도구를 나누어 기록합니다

DEPENDENCY SCOPE

의존성은 실행에 쓰는 패키지와 개발 도구를 나누어 기록합니다

Express는 서비스 실행에, nodemon·Webpack은 개발·빌드에 쓰이며 package.json의 다른 필드에 기록합니다. 이 분류와 브라우저 번들에 실제 포함되는 코드는 별도로 확인합니다.

실행과빌드의직접의존성을나눈다프로젝트의 직접 의존성을 서비스 실행에 쓰는 Express와 개발·빌드에 쓰는 Webpack으로 나누며, 이 필드 분류와 브라우저 번들에 포함되는 코드는 별도로 확인합니다.프로젝트 manifest요구 범위Express서비스 런타임Webpack개발·빌드 도구
실행과빌드의직접의존성을나눈다프로젝트의 직접 의존성을 서비스 실행에 쓰는 Express와 개발·빌드에 쓰는 Webpack으로 나누며, 이 필드 분류와 브라우저 번들에 포함되는 코드는 별도로 확인합니다.프로젝트 manifest요구 범위Express서비스 런타임Webpack개발·빌드 도구

dependencies · 서비스 실행

npm install express

서버가 요청을 처리할 때 Express를 사용합니다. 패키지명과 요구 버전 범위가 dependencies에 기록됩니다.

devDependencies · 개발·빌드

npm install nodemon --save-dev

nodemon은 개발 중 서버 재시작을 돕습니다. Jest·Webpack 같은 테스트·빌드 도구도 이 필드에 기록합니다.

직접 의존성 → 전이 의존성

프로젝트가 요구한 패키지가 다시 다른 패키지를 요구하면 npm이 그 관계를 해결합니다. 단순히 package.json에 적힌 이름 수만 설치되는 것이 아닙니다.

manifest와 lock의 역할

package.json은 요구한 범위를, package-lock.json은 해결된 정확한 버전과 의존성 트리를 기록합니다. 개발 단계에서 필요한 도구도 빌드 환경에는 설치해야 합니다.

peerDependencies

호스트 패키지와 함께 사용할 호환 버전 범위입니다. npm 7 이상은 기본적으로 peer 패키지를 자동 설치하며, 충돌 여부도 확인합니다.

optionalDependencies

설치 실패를 허용하는 의존성입니다. 빠져도 프로그램이 동작하도록 처리해야 하며, 생략 여부는 설치 옵션·환경에 따라 달라질 수 있습니다.

번들 포함 여부는 import와 빌드 설정으로 확인

dependencies와 devDependencies라는 필드만으로 브라우저에 보내는 코드를 결정하지 않습니다. 실제 import, 번들러 설정과 빌드 결과를 함께 확인합니다.

package.json 파일은 npm을 사용하는 모든 Node.js 프로젝트의 핵심 설정 파일입니다.

이 파일은 프로젝트에 대한 메타데이터와 패키지 의존성, 스크립트 등을 정의합니다.

package.json 파일 생성

새로운 프로젝트를 시작할 때 npm init 명령어를 사용하여 package.json 파일을 생성할 수 있습니다.

mkdir my-nodejs-app
cd my-nodejs-app
npm init

이 명령어를 실행하면 프로젝트 이름, 버전, 설명 등을 입력하라는 프롬프트가 나타납니다.

모든 질문에 기본값으로 응답하려면 -y 플래그를 사용합니다.

npm init -y

아래는 생성된 package.json을 바탕으로 설명용 값과 start 스크립트를 추가한 예시입니다. npm init이 이 내용을 그대로 만드는 것은 아닙니다.

{
  "name": "my-nodejs-app",
  "version": "1.0.0",
  "description": "My first Node.js app",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "keywords": [],
  "author": "Your Name <[email protected]>",
  "license": "ISC",
  "dependencies": {},
  "devDependencies": {}
}

주요 필드 설명

  • name: 프로젝트의 이름입니다. npm 레지스트리에 발행할 경우 고유해야 합니다.
  • version: 프로젝트의 현재 버전입니다. SemVer(Semantic Versioning) 규칙을 따르는 것이 일반적입니다. (예: 1.0.0)
  • main: 프로젝트의 진입점(Entry Point) 파일입니다. require('my-nodejs-app')처럼 프로젝트를 모듈로 임포트할 때 사용되는 파일입니다.
  • scripts: 프로젝트에서 자주 사용하는 명령어들을 정의합니다. npm run <script-name> 형태로 실행합니다. start, test 등 일부 스크립트는 run 없이 npm start, npm test로 바로 실행할 수 있습니다.
  • dependencies: 프로젝트가 실제 운영 환경(프로덕션 환경)에서 실행될 때 필요한 패키지들입니다. (예: react, express, axios)
  • devDependencies: 프로젝트를 개발, 테스트, 빌드할 때 필요한 패키지들입니다. 완성된 산출물을 실행하는 환경에서는 보통 생략하지만, 운영 서버라도 원 소스를 새로 빌드한다면 해당 빌드 도구가 필요합니다. (예: nodemon, jest, webpack)

npm 명령어 사용법

패키지 설치

  • 일반적인 패키지 설치 (dependencies)
    npm install express    # express 패키지 설치 (최신 버전)
    npm i react            # 'install'을 'i'로 줄여 쓸 수 있음
    npm install axios@^1.6.0 # 특정 버전 범위의 패키지 설치

    이 명령어를 실행하면 패키지가 node_modules 디렉터리에 설치되고, package.json 파일의 dependencies 섹션에 패키지 이름과 버전 정보가 자동으로 추가됩니다. 위 주석의 “최신 버전”은 모든 공개 버전 중 가장 큰 번호를 뜻하지 않습니다. 버전·태그를 생략하면 기본적으로 레지스트리의 latest 태그를 기준으로 선택하며, npm 버전과 현재 Node.js에 대한 호환 조건도 확인합니다. npm install의 버전 선택을 참고합니다.

  • 개발용 패키지 설치 (devDependencies)
    npm install nodemon --save-dev
    npm i jest -D # '--save-dev'를 '-D'로 줄여 쓸 수 있음

    이 명령어는 패키지를 node_modules에 설치하고, package.json 파일의 devDependencies 섹션에 추가합니다.

  • 전역 설치 (Global Installation) 일부 패키지는 프로젝트 내에서 사용하는 것이 아니라, 시스템 전체에서 CLI(Command Line Interface) 도구로 사용하기 위해 전역으로 설치합니다. (예: npm-check-updates)

    npm install -g npm-check-updates

    주의: 전역 설치는 가급적 피하고, 가능하면 npx 기반 실행 또는 프로젝트 로컬 설치를 우선합니다.

  • 모든 의존성 설치 프로젝트를 처음 클론하거나, 다른 개발자가 package.json 파일을 수정하여 새로운 의존성을 추가했을 때 사용합니다.

    npm install # package.json에 명시된 모든 dependencies 및 devDependencies 설치

    위 예시는 개발 의존성을 생략하지 않은 기본 개발 환경을 전제로 합니다. NODE_ENV=production이거나 --omit=dev 옵션을 사용하면 개발 의존성을 디스크에 설치하지 않을 수 있습니다. 따라서 lock에 Webpack이 기록되어 있어도 해당 환경의 node_modules에 실제로 있는지는 따로 확인해야 합니다. --include=dev로 명시한 범위는 생략 옵션보다 우선합니다.

패키지 제거

npm uninstall express
npm uninstall jest -D

패키지 업데이트

npm update # 모든 패키지를 package.json에 명시된 버전 범위 내에서 업데이트
npm update express # 특정 패키지 업데이트

팁: npm outdated 명령어를 사용하면 현재 설치된 패키지 중 업데이트가 가능한 패키지 목록을 확인할 수 있습니다.

SemVer (Semantic Versioning): 1.2.3과 같은 버전 번호는 메이저.마이너.패치를 의미합니다.

  • ~1.2.3 (틸드): 1.2.x 버전까지 업데이트 허용. (패치 버전만 업데이트)
  • ^1.2.3 (캐럿): 1.x.x 버전까지 업데이트 허용. (마이너, 패치 버전 업데이트)

위 표현에는 시작 버전 이상의 값이라는 하한도 있습니다. 안정 버전만 비교하면 ~1.2.3은 >=1.2.3 <1.3.0, ^1.2.3은 >=1.2.3 <2.0.0입니다. 캐럿의 0.x 범위는 다르게 읽습니다. ^0.2.3은 >=0.2.3 <0.3.0이고 ^0.0.3은 >=0.0.3 <0.0.4입니다. prerelease를 포함한 일반 규칙은 npm의 semver 구현 설명을 확인합니다.

예시

프로젝트가 요구한 ^1.2.3에 lock이 1.2.8을 기록했고 registry에는 1.9.0과 2.0.0도 있다고 가정합니다. 일치하는 lock을 재현하는 npm ci의 선택은 1.2.8입니다. 범위 안에서 의도적으로 업데이트하면 1.9.0은 후보가 될 수 있으나 2.0.0은 이 범위 밖입니다. “범위에 들어감”과 “현재 lock에 기록됨”은 서로 다른 질문입니다.

독립 확인

안정 버전 1.2.2·1.2.9·1.3.0 중 ~1.2.3에 들어가는 값을 고르세요. ^0.2.3에는 0.2.9와 0.3.0 중 어느 값이 들어가나요? package.json의 요구 버전을 바꾸고 lock은 그대로 둔 경우 npm ci가 새 조합을 자동 선택해야 하나요?

풀이 확인

~1.2.3에는 1.2.9만 들어갑니다. 1.2.2는 하한보다 낮고 1.3.0은 상한에 닿습니다. ^0.2.3에는 0.2.9만 들어갑니다. manifest와 lock이 불일치하면 npm ci는 오류를 내며 lock을 고쳐 새 조합을 고르는 명령이 아닙니다. 의도한 변경 환경에서 두 파일을 함께 갱신하고 검증해야 합니다.

따라서 npm update는 일반적으로 package.json에 명시된 버전 범위 내에서만 업데이트를 수행합니다.

메이저 버전 업데이트 등 더 큰 업데이트를 원하면 npm install <package-name>@latest 또는 npm-check-updates (패키지)와 같은 도구를 사용해야 합니다.

스크립트 실행

package.json의 scripts 섹션에 정의된 명령어를 실행합니다.

npm run test
npm run start # 'start' 스크립트는 'run' 생략 가능

이 예제의 test는 아직 테스트를 설정하지 않았다는 메시지와 종료 코드 1을 내는 자리 표시자입니다. start를 사용하려면 실행할 index.js를 먼저 작성해야 합니다.

package-lock.json 파일

npm install 명령어를 실행하면 package-lock.json 파일이 자동으로 생성됩니다.

이 파일은 npm이 해결한 패키지들의 정확한 버전, 의존성 트리, 다운로드 URL 등의 상세 정보를 기록합니다. 설치 옵션으로 디스크에서 생략한 개발 의존성도 lock에는 기록될 수 있으므로, 현재 node_modules의 파일 목록과 항상 같지는 않습니다.

  • 왜 필요한가?: package.json의 버전 범위(^1.2.3)만으로 의존성을 관리하면, 여러 개발자가 동일한 npm install 명령어를 실행했을 때, 각기 다른 최신 패치/마이너 버전이 설치되어 결과적으로 다른 환경이 구축될 위험이 있습니다. package-lock.json은 해결된 정확한 버전과 의존성 트리를 기록합니다. 재현 설치에는 manifest와 lock이 일치해야 하는 npm ci를 사용하며, Node·npm 버전과 설치 옵션·운영체제도 함께 관리합니다. 잠금 파일만으로 모든 실행 환경이나 빌드 결과가 동일해지는 것은 아닙니다.
  • Git 관리: package-lock.json 파일은 반드시 Git 버전 관리 대상에 포함시켜야 합니다.

package.json과 package-lock.json, 그리고 npm 명령어가 함께 만드는 재현 가능한 흐름은 다음과 같습니다.

의존성 변경은 install로, 잠금 파일 재현은 ci로 구분합니다

REPRODUCIBLE INSTALL

의존성 변경은 install로, 잠금 파일 재현은 ci로 구분합니다

package.json과 package-lock.json을 함께 관리합니다. npm ci는 기존 lock과 manifest가 맞아야 실행되며, 둘이 다르면 새 버전을 고르는 대신 실패합니다.

lock을검증한설치뒤앱을별도검증한다기존 lock과 manifest의 요구가 불일치하면 오류로 끝나고, 일치하면 npm ci로 lock을 재현한 뒤 빌드와 테스트로 앱의 결과를 별도 확인합니다.manifest + lock기존 lock요구와 lock일치하는가?실패변경 환경 확인npm ci 설치lock 그대로별도 빌드·테스트앱 결과 확인불일치일치
lock을검증한설치뒤앱을별도검증한다기존 lock과 manifest의 요구가 불일치하면 오류로 끝나고, 일치하면 npm ci로 lock을 재현한 뒤 빌드와 테스트로 앱의 결과를 별도 확인합니다.manifest + lock기존 lock요구와 lock일치하는가?실패변경 환경 확인npm ci 설치lock 그대로별도 빌드·테스트앱 결과 확인불일치일치

변경을 만드는 환경

npm install express
# 또는 npm update express

① 요구 범위에 맞춰 의존성을 해결합니다.

② manifest·lock의 변경을 검토합니다.

③ 빌드·테스트하고 두 파일을 함께 공유합니다.

동료·CI에서 재현하는 환경

npm ci
npm run build
npm test

① 기존 lock이 필요하며 manifest와 비교합니다.

② 기존 node_modules를 제거한 뒤 lock을 기준으로 설치합니다.

③ manifest·lock을 수정하지 않고 빌드·테스트합니다.

manifest와 lock이 불일치하면

npm ci가 오류를 냅니다. 의도한 의존성 변경인지 확인하고, 변경 환경에서 올바른 lock을 만든 뒤 다시 검증합니다.

빌드·테스트가 실패하면

설치 오류와 코드 오류를 구분합니다. 모든 실패를 의존성 업데이트로 해결하지 않고, 해당 원인을 수정한 뒤 다시 실행합니다.

재현 범위도 함께 남깁니다

Node·npm 버전, .npmrc 옵션, 운영체제와 설치 스크립트도 결과에 영향을 줍니다. lock은 해결된 의존성 버전의 기준이며, 그 자체로 동일한 빌드 산출물까지 보장하지는 않습니다.

패키지를 추가하거나 업데이트할 때는 설치 명령 자체보다 의존성 위치, 잠금 파일 변경, 실행 검증을 함께 확인해야 합니다.

npm ci의 계약은 기존 lock 필요, manifest 불일치 실패, 기존 node_modules 제거, manifest·lock을 쓰지 않는 설치를 구별합니다. lock을 만들 때 사용한 의존성 트리 옵션도 재현 환경에서 맞춥니다. npm ci가 성공한 뒤 빌드가 실패하면 “설치 성공”을 “앱 검증 성공”으로 보고하지 않습니다.

Webpack은 devDependencies에 있어도 브라우저용 산출물을 만드는 환경에는 필요합니다. 개발 의존성을 생략한 서버 실행 환경과, 원 소스를 받아 새로 빌드하는 환경을 구별하세요. 반대로 dependencies에 적힌 모든 패키지가 브라우저에 전송되는 것도 아닙니다. 실제 import 관계와 번들 결과를 따로 확인합니다. 의존성 필드의 역할은 패키지를 사용하는 시점을 기준으로 설명합니다.

독립 확인

express 서버와 Webpack 빌드 도구가 있는 프로젝트에서 개발 의존성을 생략한 설치는 성공했습니다. 그 환경에서 원 소스를 새로 번들링하려면 무엇이 빠졌을 수 있나요? 이 장의 기본 package.json에서 npm test가 종료 코드 1을 내면 어떤 테스트가 실패했다고 단정할 수 있나요?

풀이 확인

Webpack 등 빌드 도구가 빠졌을 수 있으므로 빌드 환경의 설치 범위를 확인합니다. 원 scripts.test는 아직 테스트가 없다는 메시지와 exit 1을 내는 자리 표시자입니다. 실제 단위 테스트를 실행해 특정 기능이 실패한 결과와 구별해야 합니다.


Yarn (선택 사항)

Yarn은 Facebook(현 Meta)에서 개발한 또 다른 JavaScript 패키지 관리자입니다.

npm과 유사한 기능을 제공하지만, 일부 면에서 개선된 점을 제공합니다.

  • 주요 특징
    • 빠른 속도: 패키지 설치 시 병렬 처리 및 캐싱을 통해 npm보다 빠를 수 있습니다.
    • 결정론적 설치: yarn.lock에 의존성 버전을 기록합니다. 도구 버전과 설치 옵션도 맞추고 lockfile을 유지하는 설치 정책을 사용해야 같은 조합을 재현할 수 있습니다.
    • 오프라인 모드: 이전에 다운로드한 패키지를 캐시하여 인터넷 연결 없이도 설치할 수 있습니다.
  • 명령어: npm과 유사하지만 약간 다릅니다.
    • npm install -D <package-name> -> yarn add --dev <package-name>
    • npm install <package-name> -> yarn add <package-name>
    • npm uninstall <package-name> -> yarn remove <package-name>
    • npm install (모든 의존성) -> yarn install
    • npm run <script> -> yarn <script>
  • 선택 가이드: npm이 꾸준히 발전하여 Yarn과의 성능 차이가 많이 줄어들었지만, 여전히 Yarn을 선호하는 개발자나 팀이 많습니다. 어느 것을 사용해도 무방하며, 중요한 것은 팀 내에서 일관된 도구를 사용하는 것입니다. 이 책에서는 npm을 기본으로 사용합니다.

어떤 패키지 관리자를 쓰든 핵심은 같은 버전 조합과 실행 명령을 팀 전체가 재현할 수 있게 남기는 것입니다.


패키지 관리 정리

핵심은 의존성 범위를 명확히 하고, 재현 가능한 빌드를 유지하는 운영 습관을 만드는 것입니다.

이번 장에서는 모던 웹 개발의 필수 도구인 npm (Node Package Manager)과 패키지 관리에 대해 심층적으로 학습했습니다.

package.json은 프로젝트 메타데이터와 의존성을 정의하는 핵심 파일입니다.

이 장에서는 dependencies와 devDependencies의 차이, npm install, npm uninstall, npm update, npm run 흐름, package-lock.json의 역할과 SemVer 규칙을 정리했습니다.

npm은 외부 라이브러리, 스크립트, 잠금 파일을 통해 개발 환경의 일관성을 관리하는 도구입니다.

패키지는 필요성, 유지보수 상태, 번들 영향, 보안 이슈를 함께 확인한 뒤 추가해야 합니다.