본문 바로가기
BackEnd

Node.js의 기능, Npm이란?

by SoriKim 2023. 11. 3.
반응형

1. Node.js module system

Node.js에 가장 근본적이고 중요한 Node.js 모듈 시스템에 대해 알아보겠습니다. 

module은 코드의 조각으로 여러 모듈이 조합되어 하나의 소프트웨어를 이루게 됩니다. 

즉, 프로그램을 만들 때 코드를 잘 모듈화해서 만들게 되면 유지보수 하기 쉬운 구조로 시스템을 만들 수 있습니다. 

결국 모듈화가 잘 된 코드는 재사용성과 확장성을 높여 새로운 기능을 개발하거나 유지보수 할 때, 전체적인 비용을 감소시키며 개발팀의 생산성을 증대시킬 수 있습니다. 그만큼 모듈화는 지속 가능한 소프트웨어 개발에 있어 중요한 부분입니다. 

 

초기 Javascript는 모듈을 위한 명시적인 키워드, 패키징 정책을 지원하지 않아 모듈화와는 거리가 먼 언어였습니다. 

ES6이 등장하면서 조금 달라지기는 했지만 개발자들에게는 Javascript의 모듈화는 범용적인 사용을 위해 반드시 해결해야 하는 과제였습니다. 

 

이 과제를 해결하고자 하는 노력은 이전부터 계속 되어 왔지만 CommonJS라는 Javascript를 브라우저 뿐 아니라 서버사이드 애플리케이션, 데스크톱에서도 사용하려고 조직한 자발적 워킹 그룹에서 가장 의미있는 결과를 만들어냈습니다. 

CommonJS의 'Common'은 Javascript를 브라우저에서만 사용하는 언어가 아닌 일반적인 범용 언어로 사용할 수 있게 만들겠다는 의지를 나타낸 것이라 이해할 수 있습니다. 

 

이렇게 범용언어로 사용하기 위해 '명세(Specification)'를 만들었고, Node.js 모듈 시스템도 CommonJS의 모듈 Specification를 따르고 있습니다. Node.js는 이런 모듈화 작업 덕분에 탄생할 수 있었습니다. 

 

CommonJS의 모듈명세는 모듈을 어떻게 정의하고, 사용할 것인지에 대한 것으로 모듈화에 필요한 것은 크게 3가지 입니다. 첫 번째는 모든 모듈은 자신만의 독립적인 실행 영역이 있어야 하는 것입니다. 

- 이 말은 전역변수(Global variable)와 지역변수(Local variable)를 분리하는 것을 의미합니다. 

서버 사이드 JavaScript의 경우 파일마다 독립적인 파일 스코프가 있기 때문에 파일 하나에 모듈 하나를 작성하는 방법으로 독립적인 실행 영역을 만듭니다. 

두 번째는 모듈을 외부에서 사용할 수 있도록 공개하는 것으로 Node.js(CommonJS 모듈 명세에 따름)에서 exports라는 전역 객체를 이용해 정의합니다. 

세 번째는 모듈을 사용하는 영역에서는 require() 함수를 이용해서 모듈을 불러옵니다. 

 

2. Creating Custom module 

CommonJS의 Module 명세 방법을 따르는 Node.js에서 모듈 시스템은 실제 어떻게 정의하고 사용할 수 있는지 알아보겠습니다. Module은 파일과 1대 1의 대응관계를 가지며 하나의 모듈은 자신만의 독립적인 실행 영역(Scope)를 가지게 됩니다. 

 

모듈은 module.exports 또는 exports 객체를 통해 정의하고 외부로 공개하고, 공개된 모듈은 require 함수를 사용해 Import 할 수 있습니다. 

 

1️⃣ exports 객체를 사용해 만드는 방법

모듈을 파일로 작성해 외부에 공개할 대상을 exports 객체의 property(일부 객체 지향 프로그래밍 언어에서 필드와 메소드 간 기능의 중간이 클래스 멤버의 특수한 유형)또는 Method를 정의하는 방법이 있습니다. 

// calculator.js
const initialNumber = 0 

// export 객체에 add method, substract method 정의 
exports.add = (x, y) => initialNumber + x + y ;
exports.substract = (x, y) => initialNumber + x - y ;

 

위의 내용처럼 exports 객체의 메소드로 정의 했으며, 변수 number는 calculator 모듈에서만 유효한 private 변수가 되고, add와 substract 메소드는 외부에 공개되어 필요한 곳에서 사용할 수 있습니다. 이제 정의한 메소드를 필요한 곳에서 require() 함수를 사용해 임의의 이름으로 circle 모듈을 import 할 수 있습니다. 

 

// main.js
const calculator = require("./calculator.js");

console.log(`1 + 5 =  ${calculator.add(1, 5)}`);
console.log(`1 - 5 =  ${calculator.subtract(1, 5)}`);

위 main.js에 require 함수 호출 결과 calculator 모듈은 객체 형태로 반환되어 calculator변수에 담기게 됩니다. 

이렇게 calculator.add, calculator.subtract과 같은 형식으로 공개된 calculator 모듈을 참조합니다. 

 

2️⃣ module.exports를 사용해 만드는 방법

1번에서 살펴본 exports 객체는 여러 개의 속성과 메소드를 정의할 수 있었습니다. 

하지만 module.exports에는 하나의 객체만 할당할 수 있습니다. 

 

// calculator.js
const initialNumber = 0

function add(a, b) {
  return a + b;
}

function substract(a, b) {
  return a - b;
}

module.exports = {
	add,
	substract
}

 

// main.js

const calculator = require("./calculator.js");

console.log(`1 + 5 =  ${calculator.add(1, 5)}`);
console.log(`1 - 5 =  ${calculator.substract(1, 5)}`);

require 함수를 통해 calculator 모듈을 import하여 calculator 변수에 할당했습니다. 

아래와 같이 구조분해 할당을 활용해 import 해서 사용할 수도 있습니다.

// main.js
const {add, substract} = require("./calculator.js");

console.log(`1 + 5 =  ${add(1, 5)}`);
console.log(`1 - 5 =  ${substract(1, 5)}`);

 

3. Reading and Writing Files - Synchronously

Node.js는 브라우저에서 JavaScript로 할 수 없는 많은 일들을 할 수 있게 해줍니다. 대표적으로 파일 시스템에서 파일을 읽고 쓰는 기능입니다. 

 

이 기능을 구현하기 위해 Node.js에서 기본으로 제공하는 파일 시스템 모듈을 사용하면 쉽게 구현이 가능합니다. 

require() 함수를 이용해 FS 모듈을 호출하면, 모듈 안에 우리가 사용할 수 있는 많은 함수들이 있는 객체가 반환됩니다. 

반환된 객체를 fileSystem 변수에 담아 파일 시스템에 데이터를 읽고 쓰는 기능이 필요할 때마다 사용할 수 있습니다. 

//syncFileSystem.js

const fileSystem = require('fs')

 

// user.txt 

Name: Jane
Age: 23

 

// syncFileSystem.js

const fileSystem = require("fs");

//Read File (Before)
const userInfo = fileSystem.readFileSync("./user.txt", "utf-8");
console.log(`===== Before written ===== \n${userInfo}`);

//Write File
const additionalInfo = `${userInfo}PhoneNumber : 010-7777-0000\nModified at : ${Date.now()}`;
fileSystem.writeFileSync("./user.txt", additionalInfo);
console.log("===== Successfully File written! ===== \n");

//Read File (After)
const updatedPrivateInformation = fileSystem.readFileSync("./user.txt", "utf-8");
console.log(`===== After written ===== \n${updatedPrivateInformation}`);

//추가적으로 실행되어야 하는 코드 (동기적 실행을 이해하기 위해서 추가된 코드 입니다.) (1)
console.log("추가적으로 실행되어야 하는 코드 1"); 
console.log("추가적으로 실행되어야 하는 코드 2");
console.log("추가적으로 실행되어야 하는 코드 3");
console.log("추가적으로 실행되어야 하는 코드 4");

위의 코드처럼 fileSystem 객체를 사용해 직접 파일 시스템에 저장되어 있는 user.txt에 접근해 데이터를 읽고, 쓰는 작업을 했습니다. fileSystem.readFileSync() 메서드를 사용해 파일에 접근하며, Sync 라는 용어가 등장하는데 이는synchronic(동기)를 의미합니다. 즉 readFileSync() 함수는 동기적으로 실행된다는 의미입니다. 

그렇기 때문에 함수가 호출되어 실행된 후 값을 반환할 때까지 기다린 후 다음 코드를 실행하게 됩니다.

 

이처럼 Node.js는 브라우저와 다르게 운영체제에서 기본적으로 제공하는 주된 서비스(예를 들어서, 파일 I/O 작업)들에 바인딩할 수 있는 기능을 제공해주는 것을 알 수 있습니다. 그리고 동기적 실행이 갖는 의미를 살펴봤으며 동기적 실행으로 파일 시스템에 접근해 파일을 읽어오는 방식은 파일을 읽은 후 추가적으로 실행되야 하는 코드가 있는 경우 파일을 읽는 작업 동안 실행이 지연되므로 좋은 방식이 아닙니다. 

4. Reading and Writing Files - Asynchronously

위에서 이야기 한 바와 같이 동기적 실행은 좋은 방식이 아니며 동기적 실행의 블로킹 문제를 해결해주는 비동기적 실해에 대해 알아보도록 하겠습니다. 

 

먼저 동기란 각 코드가 한 줄 한 줄로 처리 된다는 것을 의미합니다. 

즉, 각 줄은 나머지 코드의 실행을 차단하기 때문에 특히 느린 작업(파일을 읽는데 오래 걸리는 작업 등)에서 문제가 됩니다. 

이런 문제를 비동기 함수를 사용함으로 해결할 수 있습니다. 

 

// CASE 1. Blocking, Synchronous
// syncFileSystem.js

const fileSystem = require("fs");

//Read File (Before)
const userInfo = fileSystem.readFileSync("./user.txt", "utf-8");
console.log(`===== Before written ===== \n${userInfo}`);

//Write File
const additionalInfo = `${userInfo}PhoneNumber : 010-7777-0000\nModified at : ${Date.now()}`;
fileSystem.writeFileSync("./user.txt", additionalInfo);
console.log("===== Successfully File written! ===== \n");

//Read File (After)
const updatedPrivateInformation = fileSystem.readFileSync("./user.txt", "utf-8");
console.log(`===== After written ===== \n${updatedPrivateInformation}`);

//추가적으로 실행되어야 하는 코드 (동기적 실행을 이해하기 위해서 추가된 코드 입니다.)
console.log("\n===== 추가적으로 실행되어야 하는 코드 =====");
console.log("추가적으로 실행되어야 하는 코드 1");
console.log("추가적으로 실행되어야 하는 코드 2");
console.log("추가적으로 실행되어야 하는 코드 3");
console.log("추가적으로 실행되어야 하는 코드 4");

 

위의 코드는 메서드들이 동기적으로 실행되어 실행 결과도 실행 순서와 동일하게 출력되는 것을 확인할 수 있습니다. 

 

// CASE 2. Non-blocking, Asynchronous
// asyncFileSystem.js

const fileSystem = require("fs");

// Read File (Before)
fileSystem.readFile("./user.txt", "utf-8", (err, data1) => {
  console.log(`===== Before written ===== \n${data1}`);

  const userInfo = `${data1}PhoneNumber : 010-7777-0000\nModified at : ${Date.now()}`;

  // Write File
  fileSystem.writeFile("./user.txt", additionalInfo, "utf-8", (err, data2) => {
      console.log("===== Your file has been written =====");

      // Read File (After)
      fileSystem.readFile("./user.txt", "utf-8", (err, data3) => {
        console.log(`===== After written ===== \n${data3}`);
      });
    }
  );
});

//추가적으로 실행되어야 하는 코드
console.log("\n===== 추가적으로 실행되어야 하는 코드 =====");
console.log("추가적으로 실행되어야 하는 코드 1");
console.log("추가적으로 실행되어야 하는 코드 2");
console.log("추가적으로 실행되어야 하는 코드 3");
console.log("추가적으로 실행되어야 하는 코드 4");

위 코드와 같이 fileSystem.readFile, fileSystem.writeFile 메서드를 사용해 비동기적으로(Asynchronously)실행으로 파일을 읽거나 쓸 수 있습니다. 

readFile() 메서드는 readFileSync 메서드와 다르게 인자로 파일이 저장된 경로, 인코딩, 그리고 Callback 함수를 추가로 받도록 되어 있습니다. Callback 함수는 비동기 동작 함수를 호출 할 때, 필수적으로 전달해야 하는 함수로 비동기 실행이 완료되고 호출되는 함수라고 설명할 수 있습니다. 

위 비동기적 코드를 실행해보면, 코드가 위에서부터 순차적으로 실행되는 것이 아닌 마지막 코드가 먼저 실행되고, 파일 읽기/쓰기 작업은 나중에 출력되는 것을 확인할 수 있습니다. 

 

5. NPM 이란?

Node.js 기반의 패키지를 사용하려면 npm(node package manager)이라는 패키지 관리 도구가 필요합니다. 

npm을 통해 다양한 패키지를 설치하고 버전을 관리할 수 있습니다. Node.js를 설치하면 npm이 자동으로 설치 되며 npm을 사용하기 위해서는 Node.js가 설치되어 있어야 합니다. 

 

모듈은 크게 3가지로 나눌 수 있습니다. 

1️⃣ Custom module

먼저 Custom 모듈은 Node.js CommonJS 방식으로 만들 모듈을 의미합니다. 

 

2️⃣ Built-in module

Built-in 모듈은 Node.js가 설치되면서 기본으로 내장되어 있는 모듈들을 의미합니다. 

 

3️⃣ 3rd-party module

마지막으로 3rd-party 모듈은 다른 개발자들이 만들어 놓은 모듈을 의미합니다. 

 

개발에 생산성을 높여주고 효율적으로 개발하기 위해 많이 사용합니다. 

이런 3rd-party module의 소스 코들이 모아져 있는 저장소, 웹사이트, 그리고 npm CLI를 통칭해서 NPM이라고 합니다. 

 

5-1. Package.json

Package.json 프로젝트에 대한 메타 정보와 프로젝트에서 사용중인 npm 패키지에 대한 정보를 담은 파일로 프로젝트 초기 세팅 시 Package.json부터 만들고 시작합니다. 

이때 프로젝트 별로 필요한 npm 패키지가 버전별 다를 수 있기 때문에 버전까지 명확하게 기록해야 합니다. 동일한 버전을 설치 하지 않으면 문제가 발생할 수 있습니다. 

 

🔵 Package.json에 대해

 

# project 폴더에서 아래 명령어 실행
npm init

 

# npm init이 완료되면 폴더에 package.json이 생성됨
# pacakage.json 파일
{
  "name": "api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "lordmyshepherd",
  "license": "MIT",
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "cors": "^2.8.5",
    "dotenv": "^16.0.0",
    "express": "^4.17.3",
    "jsonwebtoken": "^8.5.1",
    "mysql2": "^2.3.3",
    "typeorm": "^0.3.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.15"
  }
}

위의 과정을 통해 package.json 파일을 생성할 수 있습니다.

 

이제 package.json파일에 가장 중요한 몇 가지 속성에 대해 알아보겠습니다. 

✔️ name : 패키지 이름으로 package.json의 name 속성에 저장됩니다. 

✔️ version: 패키지의 버전으로 npm의 버전은 다소 엄격히 관리됩니다. 

✔️ scripts: node.js command line을 정의해서 사용할 수 있습니다. 

✔️ dependencies: 프로젝트에서 사용하는 배포용 패키지와 각 버전을 명시합니다. 

✔️ devDependencies: 프로젝트에서 사용하는 로컬 개발용 패키지와 각 버전이 명시되어 있습니다. 

✔️ git repository: 소스코드를 저장해둔 Git 저장소 주소를 의미합니다. 나중에 소스에 문제가 생겼을 때 사용자들이 이 저장소에 방문해 문제를 제기할 수도 있고, 코드 수정본을 올릴 수도 있습니다. package.json의 repository 속성에 저장됩니다.

✔️ keywords: 키워드는 npm 공식홈페이지에서 패키지를 쉽게 찾을 수 있게 해주며 package.json의 keywords 속성에 저장됩니다. 

✔️ license: 해당 패키지의 라이선스를 넣어주면 됩니다. MIT는 오픈소스를 의미합니다. 

 

 

5-3. node_modules

node_modules는 npm install 명령어를 실행할 때 생성되는 디렉토리를 말합니다. 

node_modules 디렉토리 내부에는 설치한 패키지들이 포함되어 있습니다. (express ... 등) 

이렇게 해당 프로젝트에 필요한 패키지들을 디렉토리에서 전부 관리할 수 있는 편리함을 제공해 줍니다. 

(express라는 라이브러리를 설치했다면 express 외에도 express와 의존 관계가 있는 패키지들이 모두 설치 됩니다.)

 

5-4. 자주 사용하는 npm 명령어 목록 

🟡 npm outdated: 사용하는 어떤 패키지에 업데이트된  내역을 확인

🟡 npm install + package: 배포용 패키지를 설치한 후 package.json의 denpendencies 속성에 추가합니다. 

🟡 npm install --save-dev + package: 개발용 패키지를 설치한 후에 package.json의 devDependencies 속성에 추가합니다. 

🟡 npm uninstall + package: 패키지를 삭제합니다. (npm rm + package 도 가능) 

🟡 npm search + 검색어: npm 패키지를 검색합니다. 

🟡 npm info + package: 패키지의 세부정보를 출력합니다. 

🟡 npm login: npm에 로그인을 하기 위해 명령어로 사전에 npmjs.com에서 회원가입이 필요합니다. 

🟡 npm whoami: 현재 사용자가 누구인지 출력합니다. 

🟡 npm logout: 로그인한 계정에서 로그아웃 합니다. 

🟡 npm version + major : package.json에서 major version 올립니다. 

🟡 npm deprecate + [package][version][message]: 패키지를 설치할 때 경고 메시지를 띄우게 합니다. 

🟡 npm publish: 만든 패키지를 npm에 배포합니다. 

🟡 npm unpublish: 만든 패키지의 배포를 중단합니다. 

 

반응형

댓글