데브코스 21일차 ( 24.11.11 월 ) Node JS / Express

워니·2024년 11월 11일
0

Programmers Front-end

목록 보기
21/27

[Section 02] Node.JS 심화


< 06. 파일 시스템 >

1. 동기 메서드와 비동기 메서드

1.1. 동기 메서드

  • 작업이 완료될 때까지 프로세스를 블로킹
  • 이로 인해 애플리케이션의 다른 작업이 잠시 멈추게 됨
  • 이벤트 루프를 차단하지 않으므로 대규모 애플리케이션에서 추천
// ex)
const fs = require("fs");
// const data = fs.readFile();
const data = fs.readFileSync(__dirname + "/sample.txt", "utf8");
console.log(data); // lorem ipsup

1.2. 비동기 메서드

  • 작업을 백그라운드로 처리하며 콜백 또는 Promise로 결과를 받는다.
// ex)
const fs = require("fs");
fs.readFile(__dirname + "/sample.txt", "utf8", (err, data) => {
  console.log(data);
}); // lorem ipsup
  • 콜백 방식 예제
fs.readFile('example.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('에러 발생:', err);
    return;
  }
  console.log('비동기 파일 읽기:', data);
});
  • Promise와 async/await
const { readFile } = require('fs').promises;

async function readExample() {
  try {
    const data = await readFile('example.txt', 'utf8');
    console.log('Promise 방식 파일 읽기:', data);
  } catch (err) {
    console.error('에러 발생:', err);
  }
}

readExample();

2. 버퍼와 스트림

2.1. Buffer(버퍼)

  • 메모리에서 직접 바이트 데이터를 처리하는 방식
  • 파일 내용을 한 번에 메모리에 올릴 때 유용
  • 대용량 데이터에는 비효율적
  • 10mb가 필요한 작업이라면 메모리에 10mb 공간을 한 번에 확보하고 진행
// ex)
const fs = require("fs");
fs.readFile(__dirname + "/sample.txt", "utf8", (err, data) => {
  console.log(data);
}); // lorem ipsup

2.2. Stream(스트림)

  • 데이터를 청크 단위로 처리하여 메모리 사용량을 줄임
  • 읽기 스티림과 쓰기 스트림으로 나눠짐
  • 10mb가 필요한 작업이라면 1mb로 잘라서 여러 번 진행하는 것
//  ex)
const fs = require("fs");

// sample.txt 파일에 스트림 방식으로 데이터 쓰기
const writeStream = fs.createWriteStream(__dirname + "/sample.txt");
writeStream.write("스트림 방식으로 쓰기", "utf8");
writeStream.end("쓰기 완료", "utf8");

// writeStream 완료 후 복사 시작
writeStream.on("finish", () => {
  console.log("파일 쓰기가 완료되었습니다.");

  // sample.txt 파일을 읽고 copy.txt로 복사하는 스트림
  const readStream = fs.createReadStream(__dirname + "/sample.txt");
  const copyStream = fs.createWriteStream(__dirname + "/copy.txt");
  readStream.pipe(copyStream);

  // 복사 완료 후 메시지 출력
  copyStream.on("finish", () => {
    console.log("파일 복사가 완료되었습니다.");
  });
});
// ex)
const fs = require("fs");
const readFile = fs.createReadStream(__dirname + "/sample.txt", "utf8");
// lorem ipsup 출력
// const readFile = fs.createReadStream(__dirname + "/sample.txt", {
//   encoding: "utf8",
//   highWaterMark: 6,
// }); // lorem 와 ipsup로 잘려서 출력
readFile.on("data", (chunk) => console.log(chunk));
readFile.on("end", () => console.log("전송 완료"));
  • 읽기 스트림 예제
const readStream = fs.createReadStream('example.txt', 'utf8');
readStream.on('data', (chunk) => console.log('청크:', chunk));
readStream.on('end', () => console.log('파일 읽기 완료'));
  • 쓰 스트림 예제
const writeStream = fs.createWriteStream('output.txt');
writeStream.write('스트림으로 쓰기 시작\\n');
writeStream.end('쓰기 완료!');
  • 스트림을 통한 파일 복사 예제
const readStream = fs.createReadStream('input.txt');
const writeStream = fs.createWriteStream('copy.txt');
readStream.pipe(writeStream);

< 07. 파일 시스템 + 이미지 >

1. fs.readFile() (버퍼 방식)

  • 이미지 파일을 읽을 때 기본적으로 버퍼(Buffer) 형태로 데이터를 가져온다
const fs = require('fs');

// 이미지 파일 읽기 (비동기 방식)
fs.readFile('example.jpg', (err, data) => {
  if (err) throw err;
  console.log('이미지 파일이 버퍼로 읽힘:', data);

  // 읽은 데이터를 새로운 파일로 저장
  fs.writeFile('copy.jpg', data, (err) => {
    if (err) throw err;
    console.log('이미지 파일 복사 완료!');
  });
});

- `data`**버퍼(Buffer)** 형태의 바이너리 데이터입니다.
- 이 데이터는 그대로 복사되거나 다른 용도로 처리될 수 있습니다.

2. 버퍼를 Base64로 인코딩하기

  • 읽어들인 이미지 파일을 Base64 인코딩하여 웹 브라우저에서 직접 렌더링 가능
// ex)
const fs = require("fs");
fs.readFile(__dirname + "/sample.jpg", (err, data) => {
  const base64Image = data.toString("base64");
  const imgTag = `<img src="data:image/jpeg;base64,${base64Image}" />`;
  //   const imgTag = `<img src="data:text/plain;charset=utf-8;base64, ${base64Image}"/>`;
  console.log(imgTag);
  // data:image/jpeg는 output 창에 뜨는 주소 복사해서 브라우저에 넣으면 사진 나옴
});
// text는 text/plain 마인타입을 많이 사용 함
  • 이 방식은 이미지 데이터를 브라우저에서 직접 표시할 때 유용
  • Base64는 바이너리 데이터를 문자열로 변환하는 방법으로,
    이메일이나 JSON 데이터로 이미지를 전송할 때 유용

< 08. 이벤트 >

  • NodeJS는 서버에서 작동하기 때문에 사용자와 상호 작용한다는 개념이 없음
  • NodeJS의 이벤트는 특정 기능을 등록해서 여러 이벤트를 한 번에 수행하고 싶을 때 사용
  • 자바스크립트의 class와 비슷한 역할

1. EventEmitter 객체

  • 이벤트를 다루기 위해 events 모듈을 사용하며, 이 모듈의 EventEmitter 클래스를 통해 이벤트를 생성하고 처리
  • EventEmitter를 사용하면 이벤트를 정의하고, 발생시키며,
    해당 이벤트를 처리하는 리스너를 연결

    - 예시
        ```jsx
        // ex1)
        const EventEmitter = require("events");
        const myEvent = new EventEmitter();
        // 노드는 이벤트 인스턴스 객체를 무조건 만들어야 됨
        
        // 이벤트 등록
        // .on() 메서드 사용
        myEvent.on("greet", (name, age) => {
          console.log(`안녕! ${name}! 나이는 ${age}`);
        });
        
        // 이벤트 호출
        // .emit() 메서드 사용
        myEvent.emit("greet", "철수", 20); // 안녕! 철수! 나이는 20
        ```
        
        ```jsx
        // ex2) 추상화
        const EventEmitter = require("events");
        const readFile = new EventEmitter();
        // 노드는 이벤트 인스턴스 객체를 무조건 만들어야 됨
        readFile.on("data", (callback) => {
          callback();
        });
        readFile.emit("data", () => console.log("chunk")); // chunk
        ```
        

2. 이벤트 리스너의 주요 메서드 및 특징

2.1. on()

  • on(event, listener)
    지정한 이벤트에 리스너를 등록합니다. 이벤트가 여러 번 발생할 때마다 호출됩니다.
  • 예시
    // ex3) 사용자 커스텀 이벤트
    // 똑같은 이벤트를 여러번 적어도 오류가 아님
    // 함수와 큰 차이가 없으므로 활용할 일은 잘 없음
    // on에 대한 이해를 위해 공부하는 것
    
    const EventEmitter = require("events");
    const myEmitter = new EventEmitter();
    
    // on(나만의타입, 콜백함수)
    myEmitter.on("click", (name) => {
      console.log(`안녕하세요! 저는 ${name}`);
    });
    
    myEmitter.on("click", (name) => {
      console.log(`안녕! 나는 ${name}`);
    });
    
    // emit 직접 발생시켜야 함
    myEmitter.emit("click", "철수");
    // 안녕하세요! 저는 철수
    // 안녕! 나는 철수

2.2. once()

  • once(event, listener): 리스너를 한 번만 실행하도록 등록합니다.
  • 예시
    // ex4)
    const fs = require("fs");
    const readStream = fs.createReadStream(__dirname + "/sample.txt", "utf8");
    readStream.on("data", (chunk) => console.log(chunk)); // lorem ipsup
    readStream.on("end", () => console.log("end")); // end
    // emit으로 호출하지 않았는데도 출력이 됨
    // 이벤트를 등록해 놓으면 emit을 자동으로 호출
    
    // once
    // 한 번만 실행하도록 하고 싶을 때 사용
    const EventEmitter = require("events");
    const myEmitter = new EventEmitter();
    
    myEmitter.once("click", (name) => {
      console.log(`안녕하세요! 저는 ${name}`);
    });
    
    // emit 직접 발생시켜야 함
    myEmitter.emit("click", "철수"); // 안녕하세요! 저는 철수
    myEmitter.emit("click", "영희"); // 출력 안됨
    
    이벤트 제거
    콜백함수를 참조형태로 정의를 해줘야 가능

3. 이벤트 제거

  • 콜백 함수를 참조형태로 정의를 해줘야 제거 가능
  • 똑같은 이름의 서로 다른 이벤트를 여러 개 등록할 수도 있으므로
  • removeListener(event, listener): 특정 이벤트에 등록된 리스너를 제거합니다.
  • removeAllListeners(event): 특정 이벤트에 연결된 모든 리스너를 제거합니다.
  • 예시
    // ex)
    const EventEmitter = require("events");
    const myEmitter = new EventEmitter();
    
    const event1handler = () => {
      console.log("event1");
    };
    const event2handler = () => {
      console.log("event2");
    };
    // 똑같은 이름으로 서로 다른 이벤트를 등록할 수 있으므로
    // 명시적으로 나타내줘야 한다.
    myEmitter.on("event1", event1handler);
    myEmitter.on("event1", event2handler);
    
    myEmitter.removeListener("event1", event1handler);
    
    // 다 지우고 싶으면
    // myEmitter.removeAllListeners("event1"); // 다 삭제
    myEmitter.emit("event1"); // event2

4. 웹 소켓 내장 모듈

  • on 메서드로 connection 이벤트를 내부적으로 기다리게 함
  • connection 이벤트를 처리할 수 있게 등록만 해준다면 자체적으로 emit 발생
  • 함수는 바로 호출해야 하지만, 이런 자체적인 이벤트들은 자신에게 부여된 이벤트가 발생할 때까지 기다림
const Websocket = require("ws");
// 웹소켓 내장모듈
const wss = new Websocket.Server({ port: 8080 });
// 웹소켓서버 인스턴스 생성

// on 메서드로 connection 이벤트를 내부적으로 기다리게 함
wss.on("connection", (ws) => {
  ws.id = new Date().getTime().toString().slice(-3);
  console.log(`${ws.id}가 연결되었습니다`);

  ws.on("message", (message) => {
    console.log(`${ws.id} Received: ${message}`);
    console.log(`${ws.id} Received: ${typeof message}`);
    wss.clients.forEach((client) => {
      // console.log("client");
      // 예외 처리
      if (client !== ws) {
        client.send(message.toString());
      }
    });
  });

  ws.on("close", () => {
    console.log(`${ws.id}가 연결이 종료되었습니다`);
  });
});

console.log("WebSocket 서버가 ws:localhost:8080에서 실행 중입니다.");

[Section 03] Express 기본


09. Express 기본

1. Express 설치 및 프로젝트 초기화

1.1. Express 프로젝트 생성

mkdir express-app     # 프로젝트 폴더 생성
cd express-app        # 프로젝트 폴더로 이동
npm init -y           # 기본 설정으로 package.json 생성
  • package.json 파일이 생성되며, 이 파일은 프로젝트의 의존성과 스크립트 관리

1.2. Express 설치

npm install express
  • 이 명령어로 express 설치, package.jsondependencies 항목에 Express가 추가

2. Express 서버 만들기

2.1. 기본 서버 설정

  • 프로젝트 루트에 index.js 파일 생성
// index.js
const express = require('express');
const app = express();
const PORT = 3000;

// 기본 라우트 설정
app.get('/', (req, res) => {
  res.send('Hello, Express!');
});

// 서버 시작
app.listen(PORT, () => {
  console.log(`Server running at <http://localhost>:${PORT}`);
});

2.2. 서버 실행하기

  • 터미널에서 다음 명령어로 서버를 실행합니다:
node index.js
  • 브라우저에서 http://localhost:3000에 접속하면.
    "Hello, Express!" 메시지가 표시됩니다.

3. Express 라우팅

  • Express에서는 다양한 URL 경로와 HTTP 메서드에 따른 라우팅을 쉽게 설정할 수 있음
// index.js
app.get('/user', (req, res) => {
  res.send('User Page');
});

app.post('/user', (req, res) => {
  res.send('Create User');
});

app.put('/user', (req, res) => {
  res.send('Update User');
});

app.delete('/user', (req, res) => {
  res.send('Delete User');
});

4. 미들웨어 사용하기

  • 미들웨어는 요청-응답 주기 사이에 실행되는 함수
  • 요청이 발생되기 전에 먼저 거쳐가는 함수
  • 로깅, 인증, 에러 처리 등을 위해 사용

4.1. 기본 미들웨어

// 로깅 미들웨어
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next(); // 다음 미들웨어로 넘어가기
});

4.2. 내장 미들웨어 사용 (JSON 파싱)

// 요청 본문을 JSON으로 파싱
app.use(express.json());

app.post('/data', (req, res) => {
  res.json(req.body); // 요청 데이터 반환
});

5. 정적 파일 제공하기

  • public 폴더에 HTML, CSS, JS 같은 정적 파일을 두고 서버에서 제공합니다.

5.1 정적 파일 설정

// public 폴더의 파일을 정적 파일로 제공
app.use(express.static('public'));
  • public/index.html 파일에 다음 내용을 작성합니다:
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Express Static Example</title>
</head>
<body>
  <h1>Hello from Static File!</h1>
</body>
</html>
  • 브라우저에서 http://localhost:3000/에 접속하면 이 HTML 파일이 렌더링

6. 에러 처리

  • Express에서는 기본적인 에러 처리와 커스텀 에러 핸들러를 지원합니다.

6.1 404 에러 처리

// 404 에러 처리 (라우트가 없을 경우)
app.use((req, res) => {
  res.status(404).send('404 Not Found');
});

6.2 글로벌 에러 핸들러

// 모든 에러를 처리하는 글로벌 에러 핸들러
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

7. Nodemon으로 개발 편의성 높이기

  • 매번 서버를 재시작하지 않도록 Nodemon을 사용해 코드 변경 시 자동으로 서버를 재시작합니다.

7.1 Nodemon 설치

npm install -g nodemon

7.2 Nodemon으로 서버 실행

nodemon index.js

< 하루 정리 >

오늘도 엄청난 속도로 진도를 나가버렸다.
정신을 집중할 수 있는 최대로 집중해서 겨우 겨우 따라가는 듯 했으나,
마지막엔 도저히 따라 잡을 수 없었다.
수업이 끝나고 테스트를 봤고 엄청 쉬워 보이는 문제들이었지만,
정말 많이 틀렸다. 살면서 받아 본 적이 없는 점수라서 참 ...
하루 하루 더 떨어질 자존감이 있나 싶을 정도 바닥을 쳤는데
오늘도 역시나 어제보다 더 떨어졌다.

수업이 끝나고 프로젝트 안내를 해주시는데, 왜 그렇게 수업을 빨리 쳐냈는지 알 것 같다.
프로젝트에서 사용하는 것까지 진도를 나가기 위함이었던 것 같고,
그럼에도 불구하고 수업 내용들은 머리에 하나도 정리가 안 되어 있고 
이걸 우리가 배웠던 것만으로 지금까지 배운 그 시간만으로 도대체 할 수 있는건가 싶다.
정말 실력자를 위한 수업이다. 정말이다..

팀원들과 프로젝트 미팅을 하는데 내가 뭘 할 수 있을지도 모르겠고
미팅을 하는 와중에도 내가 할 수 있는 말이 없었다.
회사를 다닐 때 그렇게 많이 만들었던 제안서와 기획서지만,
이렇게 내가 아무 것도 할 수 있는 게 없어보이는 무기력한 느낌은 처음이라
당황스럽고 많이 작아진다.

잘 하는 팀원들이 이걸 이렇게 하자 저렇게 하자 얘기하며 고민하는 모습들이
정말 재밌어 보이고 설렘이 가득해 보인다.
나도 이 공부가 재밌긴 한데 재밌고 좋은 것만으로 버텨도 되는 건가 싶은 생각이 많이 든다.

공부한 것을 다시 정리하고 부족했던 점을 찾아서 추가 공부를 하기 위한
그런 목적의 TIL이었지만, 오늘도 강의노트와 필기한 것들을 긁어 오는 것에 그쳤고
그러면서 오늘도 역시 계속해서 내 우울함을 혼자 달래는 유일한 곳이 된 것 같다.
응원해주는 사람들이 많고 나도 열심히 하고자 하는 의욕이 가득한데,
현실을 크게 마주하니 그런 것들이 아무 소용이 없어지는 하루다.

잘 하고 있다라는 말로 나를 달래주는 것도 사치인 것 같아서
좀 많이 힘들다.
profile
첫 시작!

0개의 댓글