본문 바로가기
Back-End/Node.js

Node.js교과서 PART3

by dailycoding777 2025. 2. 13.

3강부터는 JS 파일을 실행하기

REPL이라는 콘솔을 제공한다.

Read, Evaluate , Print , Loop

읽고 |       평가      | 출력  | 반복

터미널 열고 node를 입력하면 REPL을 사용할 수 있다.

변수 선언에 관한 코드는 undefined를 뱉는다.

Last login: Mon Jan 13 21:46:51 on ttys000
☁  ~  node
Welcome to Node.js v20.12.2.
Type ".help" for more information.
> const str  = 'hello wolrd'
undefined
>

콘솔로그 해도 console.log 자체가 undefined라 undefined로 뜬다.

뭐 이런 방법이 있는데 이게 너무 불편하니까 VSCode를 사용해서 쓸 것이다

REPL이 쓸 데가 있나요?

계산기 같은 거 쓸 때 편함 ㅇㅇ

helooWorld.js를 실행 시키고 싶으면 터미널에 node helloWorld 이렇게 쓰면 된다.

js는 생략해도 되는 것임.


CommonJS 모듈 시스템(브라우저에서는 못 써요)

 

코드가 길다 싶으면 여러파일을 잘개잘개 쪼갤 수 있다.

모듈은 하나의 독립적인 코드 덩어리로, 기능을 분리하고 재사용 가능하게 만들어주는 단위다.

그러니까 React 쓸 때 export, import 했던 그 기능이다.

node가 CommonJS환경이므로 CommonJS에선 어떻게 쓰는지 알아보겠다.

es6면  export default greet

commonjs면 module.exports =  greet

이렇게인데 두가지이상 값을 넘겨줄 떄는 어떻게 하는가?

module.exports = { odd, even };

이렇게 객체형태로 보내면 되겠다.

실험해보니 배열로 보낼 수도 있다. 근데 보통 객체로 넘겨주는 게 좋다고 한다.

(JSON 떄문인듯)

☁  node-study  node func
{ odd: '홀수입니다.', even: '짝수입니다.' }
☁  node-study  node func
[ '홀수입니다.', '짝수입니다.' ]

참고로 가져올 땐 구조분해할당도 된다.

const {odd, even} = require('./var')

원래라면 아래와 같은데 ES6부터 도입이 되어 위에 코드처럼 편히 쓸 수 있는 것이다.

module.exports = { odd:odd, even:even };

그리고 module.export는 파일에서 단 한번만 써야한다.

어? 노드는 CommonJS잖아?

사실 CommonJS는 ES6문법을 직접적으로 지원하지 않지만 구문자체는 ES6문법을 사용할 수 있다고 한다.

커먼에선 되고 ES에선 안되는 현상이 있는데

이건 문법은 동일하지만 따로 설정이 필요할 수 있는 것이다.

어 그럼 왜 노드는 ES 기준의 모듈 문법을 안쓰죠?

JS 모듈 시스템이 나오기 전에 node에서 먼저 모듈을 만들어서 쓰고 있었음. (10년 넘게)

그래서 그냥 원래 쓰던 모듈 시스템을 사용한다.

설정이 필요할 수도 있을 뿐 ,

그렇다고 노드에서 export default 이런 문법이 안되는 건 아님.

import, export랑 require , module.exports가 동작 방법이 달라서

오류가 가끔 뜰 수 있다는 것만 알아두면 좋을 거 같다.


module도 생략이 가능하다.

module.exports = {odd,even}
exports.odd = odd;
exportseven = even

기본적으로 둘이 같고 빈 객체다.

module.exports == exports === {}

근데 이건 값일 때 얘기고 함수는 다르다.

function checkOddOrEven(number) {
  const realNumber = number % 2 ? odd : even;
  return realNumber;
}
module.exports = checkOddOrEven;

이런 함수가 있으면 참조 관계가 달라져버린다.

module.exports !== exports === {}

한가지만 빼고 싶으면 하나만  module.exports로 빼자.

module.exports = checkOddOrEven

만약에 여러개를 넣고 싶다? (ex odd , even)

export.odd = odd;
export.even = even;

module.exports = {
    odd , even
}
// module.exports = {}

이런 방법으로 쓰면 된다.(이러면 참조 관계가 유지된다.)

그러니까 exports는 마지막 포장박스니까 포장박스 남발하지 말고 한번만  써라.

. CommonJS와 ES6 모듈 시스템의 차이점

Feature CommonJS ES6 Modules

구문 module.exports, require() export, import
동기/비동기 동기적 (동기적으로 모듈을 로딩) 비동기적 (모듈 로딩이 비동기적)
모듈 캐싱 자동 캐시 자동 캐시
사용 환경 주로 Node.js, 서버 사이드 브라우저, Node.js (ES6 지원)
동적 로딩 가능 (require로) 불가능 (정적인 import 문법)

Node에서의 this

일단 this가 무엇인지를 이해해야겠더라.

나는 기존에 '가르킨 곳에서의 상위 박스'라고 이해를 했었다.

this는 '현재 실행중인 코드가 속한 컨택스트'를 가르킨다.

즉, this가 가리키는 값은 함수가 어떻게 호출되는지에 따라 달라진다.

  • 브라우저 : 전역에서 window
  • Node.js : 전역컨텍스트에서 global

JS에서 그냥 this를 쓰면 window를 가르킨다.

근데 노드에서 this를 쓰면 빈 객체가 나온다..?

함수에서 써야 global이 뜬다...?

console.log(this); // result : {}

function a() {
  console.log(this === global);
}

a(); // result : true

이때문에 나는 혼돈에 빠졌다.

노드에서는 전역객체가 글로벌이니까 얘가 글로벌이지 않을까?

근데 얘는 전역스코프의 어노니머스는 글로벌이 아니고

function안에 있는 this는 글로벌이다.

빈객체가 나오는 이유가 this === module.exports 이거여서다.

노드 환경에선 각 모듈은 별도의 실행 컨텍스트로 실행된다.

그 컨텍스트 내에서 this는 module.exports를 가르킨다.

module.exports는 해당 모듈을 외부로 내보넬(export)값을 담고 있는 객체다.

그래서 모듈 내에서 this는 module.exports를 참조하게 된다.

console.log(this) //module.exports 출력

2. a() 함수 내에서 this === global

function a() {
  console.log(this === global);  // true
}

a();
  • 여기서 중요한 점은 this가 global 객체를 가리ㅣ킨다는 것이다.
  • a() 함수는 일반 함수 호출로 실행되고 있다. 일반 함수 호출에서 this는 전역객체를 가르킨다.

요약하면

모듈 내에서 this : module.exports를 가르킨다.

일반 함수 : 전역객체(global)을 가르킨다.

왜 이따구로 노드가 설계 되었는가 (궁극적 이유는 개발자 편의?)

모듈 시스템에서는 모듈의 내보낼 값을 쉽게 다루기 위해 this를 module.exports로 설정했다.

일반함수 호출에서는 this 전통 방식을 따른다.


require에 동작 원리

그냥 불러오기만 하는 방법은 아래와 같이 쓰면 된다.

require('./var')

근데 이렇게 이렇게 하면 실행만 되는거지

이 파일에서 변수 같은 것들을 가져오진 않는거다.

참고로 require도 모듈이다.

자바스크립트도 노드로 실행하면 그 파일은 모듈이 된다.

콘솔 찍어보면 로그가 이런데 main이랑 cache가 중요하다고 한다.

require의 main, cache

require를 선언하잖아? 그럼 한번 읽은 건 캐싱이 돼.

require.cache 라는 곳에서ㅇㅇ

!https://prod-files-secure.s3.us-west-2.amazonaws.com/d76682c0-0f4f-40ea-a410-86c0df396285/b19fc421-19df-4b8f-90ea-c7acf48fc462/스크린샷_2025-01-14_오후_11.24.59.png

예를 들어 노드에서 코드를 변경해도 바로 적용이 안되고 노드를 껐다가 켜야만 변경이 잘 되는데,

이게 메인과 캐싱 떄문이라고 한다.

캐시나 메인을 건드리는 건 매우 위험한 일이다. 노드를 잘 알아야 건드릴 수 있을 것이다.

그리고 많이 쓰는 일도 아니기에.

초보자가 쓸 건 아니긴 한데 알아두면 좋다.

import ,export와 다르게 require는 순서에 상관이 없다.

module.exports = '저를 찾아보세요'
require('./var')

순환참조(중급 이상)

두 객체가 서로를 참조하는 상황을 말한다.

즉 dep1은 dep2를 참조, 다시 dep2에선 dep1을 참조

// dep2에서
require(`./dep1')

// dep1에서
require(`./dep2')

Node.js에서는 모듈 시스템에서 순환참조가 발생할 수 있는데, 이때 Node.js가 모듈을 로드할 때 순환참조를 감지하고 이를 해결하기 위해 빈객체 {}로 처리할 수 있다. (노드 자체 기능임)

// fileA.js
const fileB = require('./fileB');
console.log('fileA:', fileB);

// fileB.js
const fileA = require('./fileA');
console.log('fileB:', fileA);

첫번째 코드와 같이 순환참조가 일어나는 코드다.

왜 빈 객체 {}로 반환되나?

  • 모듈 캐시: Node.js는 require()로 로드한 모듈을 캐싱하여 성능을 최적화한다.순환참조가 발생할 경우, 완전히 로드되지 않은 모듈을 빈 객체로 처리하여 순환참조 문제를 회피하는 방식이다.
  • 이미 로드된 모듈을 다시 로드하려고 할 때, 캐시된 모듈을 반환하는데,
  • 이 방법을 사용하면 무한 루프를 방지하고, 중복된 모듈 로딩을 방지하는 동시에 순환참조로 인한 오류를 피할 수 있습니다.

웬만하면 순환코드가 일어나지 않게 작성하는 게 맞다. 직관적이지 않으니까.


ECMASCript 모듈

CommonJS는 표준이 아니었다.

JS 자체에 표준이 없었기 때문에 CommonJS라는 놈을  표준으로 정해서 사용했다.

아직 노드에서 ES모듈을 100%로 사용하는 덴 무리가 있지만

ES모듈로 표준으로 대체될 것이라 한다. 그래서 나는 다 공부하면 된다!^&^

commonjs에선 mjs로 하면 ES모듈을 사용할 수 있다.

상수를 선언할 때

// js (commonjs)
const odd = '오드'
const even = '짝수'

modules.exports = {odd , even}

// mjs (ES6~)
export const odd = '오드'
export const even = '짝수'

실행 방법

node 파일명.mjs

mjs 확장자 쓰는 거 킹받는데요?

그럼 package.json 들어가서 type : "module" 설정 ㄱㄱ

import시에 mjs,js 이런거 생략이 안된다고 함. 원래 이게 당연한거라고 합니다.

이건 외우기보단 겪어보면서 하는 게 맞는 거 같다.


노드 내장 객체 알아보기

JS로 브라우저 이외에도 여러가지를 할 수 있는 이유는 사실 노드가 여러가지 기능을 제공하기 떄문이다.


Process

node는 파일시스템에 접근할 수 있었던 것처럼 운영체제,컴퓨터 언제 켜졌는지 이런거 접근할 수 있다.

process.메소드

이런식으로 하면 되는데 ,

process.cwd() //노드의 경로

!!! env가 이런 원리구나!!!

const secretId = process.env.SECRET_ID

이렇게 꺼내는 거였어..

 

setImmediate(() => {
  console.log('immediate');
});

process.nextTick(() => {
  console.log('nextTick');
});

setTimeout(() => {
  console.log('timeout');
}, 0);

Promise.resolve().then(() => console.log('promise'));

 

// 실행 결과

$ node nextTick.js
nextTick
promise
timeout
immediate

 

 

이렇게 하면 프로세스,Promise는

마이크로테스트큐라서 먼저 실행되고 이건 순서 지킴.

setImmediate , setTiemout이 환경에 따라 3,4등이 결정된다.

즉 프로세스->프로미스 -> 3,4등 환경에 따라.

process.exit(0) -> 에러없이 껏다는 건데 잘 안쓴다.

  • process.exit(1)

많이는 안쓰는데 에러 보면서 서버 끄고 싶을 때

1. process.cwd()

  • 설명: 현재 작업 디렉토리의 경로를 반환.
console.log(process.cwd());
// "/your/current/working/directory"


2. process.env

  • 설명: 환경변수를 읽거나 설정할 때 사용.
console.log(process.env.NODE_ENV); // "development" 또는 "production"
process.env.MY_VAR = "하이!";
console.log(process.env.MY_VAR); // "하이!"


3. process.argv

  • 설명: 명령어로 넘긴 인자를 배열 형태로 반환.
console.log(process.argv);
// 실행: node app.js hello world
// 출력: ['node', '/path/to/app.js', 'hello', 'world']


4. process.exit([code])

  • 설명: 현재 프로세스를 종료. code가 0이면 성공, 1이면 실패를 나타냄.
if (!process.env.MY_VAR) {
  console.error("필수 환경변수가 없습니다!");
  process.exit(1); // 실패로 종료
}


5. process.on(event, callback)

  • 설명: 프로세스 이벤트를 감지하고 처리.
  • 자주 쓰는 이벤트:
    • exit: 프로세스 종료 직전에 호출.
    • uncaughtException: 처리되지 않은 에러 발생 시 호출.
    • SIGINT: Ctrl + C로 종료 시 호출.
process.on('exit', (code) => {
  console.log(`프로세스 종료 코드: ${code}`);
});

process.on('uncaughtException', (err) => {
  console.error('에러 발생:', err.message);
});


6. process.memoryUsage()

  • 설명: 프로세스가 사용하는 메모리 정보를 반환.
console.log(process.memoryUsage());
// 출력: { rss: 123456, heapTotal: 123456, heapUsed: 123456, external: 123456 }


7. process.uptime()

  • 설명: 프로세스가 실행된 후 경과된 시간(초)을 반환.
console.log(`프로세스 실행 시간: ${process.uptime()}초`);


OS와 path

지금까지 require를 쓸 때 파일을 만들어서 , 만든 파일을 가져와서 썼다.

내가 os라는 것을 안만들었어도 노드에서 os를 제공해준다.

const os = require('os')

파일을 하나 만들고 콘솔에 찍어봤다.

const os = require("os");

console.log(os.cpus());

이 cpu가 왜 중요하냐면

싱글스레드니까 cpu를 하나밖에 사용을 안함.

10개 중 1개뺴곤 다 놀고 있음 ㅇㅇ

근데 10개인 걸 알려면 os.cpus()를 써야한다. (근데 그냥 cpu z에서 보면 되는 거 아니노..)

10코어 20스레드다 => 여기에서 나오는 쓰레드 얘기가 아니다.

이건 os의 쓰레드다.

os에 쓰레드랑 노드의 쓰레드는 다르다.

그 다음은 os.path() , os.resolve()

path 메서드는 요청 URL에 맞는 경로를 지정하는 데 사용하는 메서드다.

보통 Express.js같은 Node.js 프레임워크에서 사용된다.

API 요청에서 경로를 관리하고 라우트를 처리하는데 자주 쓰인다.

const express = require('express');
const app = express();

app.get('/example', (req, res) => {
  res.send('Hello, World!');
});

// 현재 앱의 기본 경로를 확인
console.log(app.path()); // '/'

join은 절대경로를 무시한다. '/var.js' -> 'var.js'

resolve는 진짜로 절대경로로 가버린다. 'var.js' -> 'var.js'


url,dns,searchParams

 

(WHATWG : 웹기술을 개발하고 관리하는 국제적인 그룹)

위에는 각각 회사에서 부르는 명칭이라고 한다.

이게 url에 구성 요소다.

이 표를 외우려고 하지안아도 된다. 자주 쓰다보면 외워짐

세련된 방식은 whatwg방식이라 하는데 기존 노드도 쓰이긴 쓰인다.

가끔가다가 origin주소가 사라지는 경우가 좀 있어서 쓴다고 한다.

www.naver.com/login -> /login 이렇게 말이다.

SearchParams

url 주소에 값을 넣어 Get형태지만 Post를 할 수 있는 것이다.

 

1. URLSearchParams 메서드 정리

  • getAll(키): 해당 키에 대한 모든 값을 가져옴. 예를 들어 category 키에 nodejs, javascript 두 개의 값이 들어있을 수 있음.
  • get(키): 해당 키의 첫 번째 값을 가져옴.
  • has(키): 해당 키가 존재하는지 확인.
  • keys(): searchParams의 모든 키를 반복기(iterator, ES2015 문법) 객체로 반환.
  • values(): searchParams의 모든 값을 반복기 객체로 반환.
  • append(키, 값): 해당 키에 값을 추가. 같은 키가 있으면 유지하고 새로운 값을 추가함.
  • set(키, 값): append와 비슷하지만, 기존 값들을 모두 지우고 새로운 값만 추가.
  • delete(키): 해당 키를 제거.
  • toString(): searchParams 객체를 다시 문자열로 변환. 이 문자열을 search에 대입하면 주소에 반영됨.

 

단방향 암호화 (crypto)

  • 암호화는 가능하지만 복호화는 불가능
    • 암호화: 평문(원본 텍스트)을 암호화된 값으로 변환.
    • 복호화: (단방향 암호화에서는) 암호화된 값을 원래대로 되돌릴 수 없음.
  • 단방향 암호화의 대표적인 기법: 해시 함수
    • 해시 함수: 입력 문자열을 고정된 길이의 다른 문자열로 변환하는 방식.
    • 예시: abcdefgh → qvew (변환 예시)
    • 해시 함수의 특징:
      • 같은 입력값이면 같은 출력값을 가짐.
      • 하지만 원래의 입력값을 복원할 수 없음.
  • 해시 함수 흐름
    1. 입력(비밀번호 등)
    2. 해시 함수 처리
    3. 다이제스트(출력값) 생

해시

평문을 암호같이 만들면 , 다시 평문으로 디코딩이 매우 어려움.

비밀번호에서 해시함수 많이 쓰인다.

알고리즘만 잘 선택하면 매우 안전한 방법

 

컴퓨터의 발달로 기준 암호화 알고리즘이 위협 받고 있음

  • sha512가 취약해지면 sha3으로 넘어가야함 (뭔소리?)
  • 현재 pdkdf2나 bcrypt,scrypt 알고리즘으로 비밀번호를 암호화
  • Node는 pbkdf2와 scrypt 지원

 

복호화(원래 문장대로 되돌려야 하는 경우)

저쪽이랑 내쪽이랑 같은 키를 똑같이 갖고 있어야 하는 것

근데 이게 살짝 취약 함

해커가 키를 훔칠 가능성이 매우 높음.

프론트<->서버 이렇겐 못씀. 왜냐면 프론트는 다 공개되어 있거든.

CreateCipher 쓸 땐 문제가 있어서 CreateCipheriv를 쓴다고 함.

라이브러리 중에 crypto.js가 암호화 라이브러리인데 이거 쓰면 암호화 지식 없어도 쓰기가 좀 낫다고 함.

비대칭 암호화

프론트랑 서버랑 다른 키를 갖고 있으면서 암호화,복호화 했다 할 수 있는 암호화

ex) https

RSA 방식이 좋다고 함. (너무 딥 해서 나중에 암호화 관련해서 찾아본다.)

현업에서 비밀번호를 관리하는 것을 모든 회사에서 다 애먹고 있다고 함.

AWS Key Management 이런거 쓰는 경우도 많음.

util

각종 편의 기능을 모아둔 묘듈

prromisify - 콜백함수를 Promise로 바꿔줌

콜백 방식으로 작성된 함수(Node.js의 예전 API함수)를 Promise 기반으로 반환하는데 사용.

초기에는 콜백 방식이 기본이었는데 요즘엔 async/await을 쓰는 게 편하다.

  • 콜백 지옥을 탈출하기 위해
const util = require('util');
const fs = require('fs');

const readFileAsync = util.promisify(fs.readFile);

// 이제 async/await로 사용 가능
async function readFile() {
    try {
        const data = await readFileAsync('example.txt', 'utf-8');
        console.log(data);
    } catch (err) {
        console.error(err);
    }
}

readFile();

deprecated - '이거 쓰지마셈' 경고 띄워줌

util.deprecate는 특정 함수를 더 이상 사용하지 말라고 경고 메세지를 출력해 주는데 쓰인다. (리팩토링,대체 함수로 전환하도록 유도할 때 유용)

const util = require('util');


const oldFunction = util.deprecate(() => {
    console.log('이거 곧 없어질 거야!');
}, 'oldFunction은 더 이상 사용하지 말고 새 함수를 쓰세요.');


oldFunction(); // 실행 시 경고 메시지 출력

 

콜백 문법으로 작성하면 async , await 문법을 못써서 상당히 불편한데

util.promisify로 한번 감싸면 promise가 된다.


worker,treads

노드에서 멀티스레드 방식으로 작업할 수 있게 도와주는 것임

CPU 많이 잡아먹을 때 쓰는 것이고 대부분은 싱글쓰레드로 돌리는 게 맞다.

처음에는 메인스레드 실행, 메인쓰레드 안에서 워커쓰레드를 실행 -> 워커쓰레드들한테 일을 분배한다. 그것을 다시 일을 마치면 다시 메인쓰레드로 이동.

코드를 아주 처음부터 끝까지 해야한다.

  1. 메인 스레드 시작
    • isMainThread가 true인 코드를 실행합니다.
    • 새로운 워커를 생성(new Worker(__filename)).
  2. 워커 스레드 시작
    • isMainThread가 false인 코드를 실행합니다(즉, else 블록).
  3. 메인 스레드가 메세지를 보냄
    • 메인 스레드는 워커에 "ping" 메시지를 보냅니다.
  4. 워커 스레드가 메세지를 처리
    • 워커는 메시지를 받고, "pong"을 메인 스레드로 응답합니다.
  5. 메인 스레드가 응답을 받음
    • 메인 스레드는 워커의 "pong" 메시지를 수신하고 이를 출력합니다.
  6. 워커 스레드 종료
    • 워커 스레드는 parentPort.close()를 호출하고 종료됩니다.
    • 메인 스레드는 이를 감지하고 "워커 끝" 메시지를 출력합니다.

https://inpa.tistory.com/entry/NODE-📚-workerthreads-모듈

지금은 그냥 이런 게 있다. 정도로만 알고 나중에 쓸 때 다시 찾아보자.

노드로는 굳이 안하는 게 좋고 다른 언어로 하는 게 좋다.


child process

다행이다. 워커가 너무 어려웠는데 복잡한 연산을 처리 할 다른 언어 불러서 쓸 수 있다..

spwan이라는 함수를 가져와서 사용한다.

  • 독립적인 프로세스를 생성하는 데 사용
  • 비동기로 동작하며,외부 명령이나 스크립트를 실행할 수 있게 해준다.
  • 반환값은 ChildProcess 객체다.

외부 명령을 실행하고, 입출력을 관리하는 프로세스를 생성한다.

const { spawn } = require("child_process");

// Python 파일 실행
const process = spawn("python", ["test.py"]);

// 표준 출력 처리
process.stdout.on("data", (data) => {
  console.log("출력:", data.toString());
});

// 표준 에러 처리
process.stderr.on("data", (data) => {
  console.error("에러:", data.toString());
});

// 프로세스 종료 이벤트
process.on("close", (code) => {
  console.log(`프로세스 종료, 코드: ${code}`);
});

const child = spawn(command, args, options);

  1. command: 실행할 명령어 (예: "python", "ls", "dir")
  2. args: 명령어의 인수 배열 (예: ["test.py"])
  3. options: 실행 시 설정 옵션 (예: { cwd: "/path/to/dir" })

반환값

spawn은 ChildProcess 객체를 반환합니다. 이 객체를 통해 프로세스와 상호작용할 수 있습니다:

  • child.stdout: 표준 출력 스트림
  • child.stderr: 표준 에러 스트림
  • child.stdin: 표준 입력 스트림
  • 이벤트: on('data'), on('close') 등을 사용해 데이터와 상태를 처리.

3.다른 메서드와 비교

spawn 외에도 child_process 모듈에는 외부 명령 실행을 위한 다양한 함수가 있다.

exec

  • 명령을 실행하고 결과를 버퍼로 반환.
  • 간단한 명령 실행에 적합.
const { exec } = require("child_process");

exec("ls", (err, stdout, stderr) => {
  if (err) {
    console.error("에러:", stderr);
    return;
  }
  console.log("출력:", stdout);
});

fork : Node.js 스크립트를 새로운 프로세스로 실행하고 spawn과 비슷하지만 Node.js 전용이다.


파일 시스템 사용하기

기타 모듈들

assert: 값을 비교하여 프로그램이 제대로 동작하는지 테스트하는 데 사용합니다.
dns: 도메인 이름에 대한 IP 주소를 얻어내는 데 사용합니다.
net: HTTP보다 로우 레벨인 TCP나 IPC 통신을 할 때 사용합니다.
string_decoder: 버퍼 데이터를 문자열로 바꾸는 데 사용합니다.
tls: TSL와 SSL에 관련된 작업을 할 때 사용합니다.
tty: 터미널과 관련된 작업을 할 때 사용합니다.
dgram: UDP와 관련된 작업을 할 때 사용합니다.
v8: V8 엔진에 직접 접근할 때 사용합니다.
vm : 가상머신에 직접 접근할 때 사용합니다.

fs

파일 시스템에 접근하는 모듈

  • 파일/폴더 생성,삭제.읽기,쓰기 가능
  • 웹 브라우저에서는 제한적이었으나 노드는 권한을 가지고 있음

악성 JS node를 실행하게 되면 문제가 될 수 있으니 보안상 조심해야 한다.

const fs = require("fs");
fs.readFile("./readme.md", (err, data) => {
  if (err) {
    throw err;
  }
  console.log(data);
  console.log(data.toString());
});

얘네는 콜백형식이고 인자로 1.err 2.data가 들어간다.

//readme
저를 읽어주세요

이런 결과가 나오고 이진법으로 나온다. 컴퓨터가 읽을 수 있도록. [  console.log(data); ]

toString()을 붙히면 그때야 글자가 나온다.[  console.log(data.toString()); ]

// 결과 2진법
<Buffer ec a0 80 eb a5 bc 20 ec 9d bd ec 96 b4 ec a3 bc ec 84 b8 ec 9a 94 0a>
저를 읽어주세요

fs는 promise도 지원한다. 결과는 위에 것과 같다.

const fs = require("fs").promises;
fs.readFile("./readme.md")
  .then((data) => {
    console.log(data);
    console.log(data.toString());
  })
  .catch((err) => {
    console.error(err);
  });

이번엔 생성해보겠다.

노드는 read만 읽는 게 아니고 write도 있다.

즉 파일을 생성하고 그 안에 들어갈 글을 만들 수 있는 것이다.

인자로 1.파일이름, 2.파일에 들어갈 내용이 되겠다.

const fs = require("fs").promises;
fs.writeFile("./writeme.md", "글을 입력한다!")
  .then(() => {})
  .catch((err) => {
    console.error(err);
  });

!https://prod-files-secure.s3.us-west-2.amazonaws.com/d76682c0-0f4f-40ea-a410-86c0df396285/57c6e286-40b4-44ff-825d-26eb4cfae4d8/스크린샷_2025-01-18_오후_6.49.46.png

잘 생성되었다.

자바스크립도 잘 생성했다.

응용해서 쓰고 읽는 것을 동시에 해보면

const fs = require("fs").promises;
fs.writeFile("./writeme.md", "글을 입력한다!")
  .then(() => {
    return fs.readFile("./writeme.md");
  })
  .then((data) => {
    console.log(data.toString());
  })
  .catch((err) => {
    console.error(err);
  });

then , then으로 쓰기와 읽기를 한번에 했다.

뒤에는 비동기는 원래 순서보장되지 않는다고 설명해주셨는데

Promise.then으로 순서를 살리면 되겠다.

더 깔끔히 만들고 싶으면 async ,await을 쓰고 말이다.


버퍼와 스트림 이해하기

버퍼: 일정한 크기로 모아두는 데이터

  • 일정한 크기가 되면 한 번에 처리
  • 버퍼링: 버퍼에 데이터가 찰 때까지 모으는 작업
  • 010101 이걸 16진법으로 표현했다고 함.

스트림 : 데이터 흐름

  • 일정한 크기로 나눠서 여러 번에 걸쳐서 처리
  • 버퍼(또는 청크)의 크기를 작게 만들어서 주기적으로 데이터를 전달
  • 스트리밍: 일정한 크기의 데이터를 지속적으로 전달하는 작업

대부분은 스트림이 효율적이다.

효율적으로 메모리를 사용하면서 서버에 부담을 덜 줄 수 있다.

const buffer = Buffer.from("저를 버퍼로 바꿔보세요");
console.log(buffer);
console.log(buffer.length); // 버퍼의 무게 현재는 32byte
console.log(buffer.toString());

const array = [
  Buffer.from("띄엄 "),
  Buffer.from("띄엄 "),
  Buffer.from("띄어쓰기"),
];
console.log(Buffer.concat(array).toString()); // 버퍼가 array로 들어오면 합칠 수도 있다.

버퍼방식으로 읽으려면 readfile, 긴거를 스트림으로 읽으려면 CreateReadStream

 

스트림 쓰면 버퍼보다 메모리를 아낄 수 있다.

그래서 대용량 파일서버를 하면 스트림 방식이 필수다.

스트림의 장점은 1기가 파일을 1메가씩 보낼 수 있다.

받을 때도 1메가씩 보내고 나중에 1기가로 복구를 하는 형식이다.

1메가 단위로 데이터가 흘러가는데 그 모양이 마치

파이프에 액체가 흐르는 것과 비슷하다고 해서 파이핑이라고 부른다.

이 코드는 16byte씩 나눠서 읽고,쓰는 것이다.

const fs = require("fs");

const readStream = fs.createReadStream("./readme3.md", { highWaterMark: 16 });
const writeStream = fs.createWriteStream("./writeMe.md");
readStream.pipe(writeStream);

이건 데이터를 **"읽어서 쓰는 파이프라인"**이라고 생각하면 된다.

마치 수도관 처럼

  • readStream: 수도꼭지에서 물을 뿜어냄
  • pipe(): 물이 관을 타고 흘러감
  • writeStream: 물이 도착해서 물탱크에 저장됨

before -> after

이렇게 readme3에 있던 내용 그대로 바꿀 수 있다.

16byte씩 읽어서 써준 것이다.

const fs = require("fs");
const zlib = require("zlib");

const readStream = fs.createReadStream("./readme3.md", { highWaterMark: 16 });
const zlibStream = zlib.createGzip();
const writeStream = fs.createWriteStream("./writeMeZlib.md");
readStream.pipe(zlibStream).pipe(writeStream);
//zlib을 사용하면 파일 압축 가능 16byte씩
// pipe가 모든 걸 지원하는 건 아니고 stream을 지원하는 애들만 된다.

쓰레드풀과 커스텀 이벤트

쓰레드풀

미리 여러 개의 쓰레드를 만들어 놓고, 필요할 때 꺼내 쓰는 방식

 

fs,crypto,zlib 모듈의 메서드를 실행할 때는 백그라운드에서 동시에 실행 됨

노드는 백그라운드에서 4개가 설정되어 있다.


예외 처리하기

에러로부터 노드 프로세스를 보호하기 위함.

노드는 에러와 예외가 큰 차이가 없다.

예외 : 처리하지 못한 에러

  • 노드 스레드를 멈춤
  • 노드 기본적으로 싱글 스레드라 스레드가 멈춘다는 것은 프로세스가 멈추는 것
  • 에러 처리는 필수

가장 쉬운 것은 에러가 날 거 같은 곳에 try,catch 하는 것이다.

노드가 기본적으로 제공하는 비동기메소드의 콜백에러는

에러처리 안해줘도 노드 프로세스를 멈추진 않는다.

promise에 catch를 안붙혀도 동작엔 이상이 없지만 길게 warnning이 뜬다.

에러처리는 진짜 해놓는 게 너무 좋을 거 같다.

 

모든 코드를 try,catch로 감싸는 건 너무 번거로우니까

uncaughtException을 사용하면 모든 에러가 다 이쪽으로 간다.

노드가 보장해주지 않는다고 하니 에러 고치는 코드로 쓰지 말아라.

보는 용도로만 쓰고 언넝 고치러 가자.

언넝 고치러 가자.

'Back-End > Node.js' 카테고리의 다른 글

Node.js 교과서 PART2 (2-1)  (0) 2025.02.13
Node.js 교과서 PART2 (2)  (0) 2025.01.28
Node.js 교과서 PART2 (1)  (0) 2025.01.19
Node.js 교과서 PART1  (0) 2025.01.18
다시 시작하는 Node.js, 새로 시작하는 CS  (0) 2025.01.11