[Section 02] Node.JS 심화
< 06. 파일 시스템 >
1. 동기 메서드와 비동기 메서드
1.1. 동기 메서드
- 작업이 완료될 때까지 프로세스를 블로킹
- 이로 인해 애플리케이션의 다른 작업이 잠시 멈추게 됨
- 이벤트 루프를 차단하지 않으므로 대규모 애플리케이션에서 추천
const fs = require("fs");
const data = fs.readFileSync(__dirname + "/sample.txt", "utf8");
console.log(data);
1.2. 비동기 메서드
- 작업을 백그라운드로 처리하며 콜백 또는
Promise
로 결과를 받는다.
const fs = require("fs");
fs.readFile(__dirname + "/sample.txt", "utf8", (err, data) => {
console.log(data);
});
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('에러 발생:', err);
return;
}
console.log('비동기 파일 읽기:', data);
});
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 공간을 한 번에 확보하고 진행
const fs = require("fs");
fs.readFile(__dirname + "/sample.txt", "utf8", (err, data) => {
console.log(data);
});
2.2. Stream
(스트림)
- 데이터를 청크 단위로 처리하여 메모리 사용량을 줄임
- 읽기 스티림과 쓰기 스트림으로 나눠짐
- 10mb가 필요한 작업이라면 1mb로 잘라서 여러 번 진행하는 것
const fs = require("fs");
const writeStream = fs.createWriteStream(__dirname + "/sample.txt");
writeStream.write("스트림 방식으로 쓰기", "utf8");
writeStream.end("쓰기 완료", "utf8");
writeStream.on("finish", () => {
console.log("파일 쓰기가 완료되었습니다.");
const readStream = fs.createReadStream(__dirname + "/sample.txt");
const copyStream = fs.createWriteStream(__dirname + "/copy.txt");
readStream.pipe(copyStream);
copyStream.on("finish", () => {
console.log("파일 복사가 완료되었습니다.");
});
});
const fs = require("fs");
const readFile = fs.createReadStream(__dirname + "/sample.txt", "utf8");
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 인코딩하여 웹 브라우저에서 직접 렌더링 가능
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}" />`;
console.log(imgTag);
});
- 이 방식은 이미지 데이터를 브라우저에서 직접 표시할 때 유용
- 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)
지정한 이벤트에 리스너를 등록합니다. 이벤트가 여러 번 발생할 때마다 호출됩니다.
- 예시
const EventEmitter = require("events");
const myEmitter = new EventEmitter();
myEmitter.on("click", (name) => {
console.log(`안녕하세요! 저는 ${name}`);
});
myEmitter.on("click", (name) => {
console.log(`안녕! 나는 ${name}`);
});
myEmitter.emit("click", "철수");
2.2. once()
once(event, listener)
: 리스너를 한 번만 실행하도록 등록합니다.
- 예시
const fs = require("fs");
const readStream = fs.createReadStream(__dirname + "/sample.txt", "utf8");
readStream.on("data", (chunk) => console.log(chunk));
readStream.on("end", () => console.log("end"));
const EventEmitter = require("events");
const myEmitter = new EventEmitter();
myEmitter.once("click", (name) => {
console.log(`안녕하세요! 저는 ${name}`);
});
myEmitter.emit("click", "철수");
myEmitter.emit("click", "영희");
이벤트 제거
콜백함수를 참조형태로 정의를 해줘야 가능
3. 이벤트 제거
- 콜백 함수를 참조형태로 정의를 해줘야 제거 가능
- 똑같은 이름의 서로 다른 이벤트를 여러 개 등록할 수도 있으므로
removeListener(event, listener)
: 특정 이벤트에 등록된 리스너를 제거합니다.
removeAllListeners(event)
: 특정 이벤트에 연결된 모든 리스너를 제거합니다.
- 예시
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.emit("event1");
4. 웹 소켓 내장 모듈
on
메서드로 connection
이벤트를 내부적으로 기다리게 함
connection
이벤트를 처리할 수 있게 등록만 해준다면 자체적으로 emit
발생
- 함수는 바로 호출해야 하지만, 이런 자체적인 이벤트들은 자신에게 부여된 이벤트가 발생할 때까지 기다림
const Websocket = require("ws");
const wss = new Websocket.Server({ port: 8080 });
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) => {
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
파일이 생성되며, 이 파일은 프로젝트의 의존성과 스크립트 관리
1.2. Express 설치
npm install express
- 이 명령어로
express
설치, package.json
의 dependencies
항목에 Express가 추가
2. Express 서버 만들기
2.1. 기본 서버 설정
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 메서드에 따른 라우팅을 쉽게 설정할 수 있음
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 파싱)
app.use(express.json());
app.post('/data', (req, res) => {
res.json(req.body);
});
5. 정적 파일 제공하기
public
폴더에 HTML, CSS, JS 같은 정적 파일을 두고 서버에서 제공합니다.
5.1 정적 파일 설정
app.use(express.static('public'));
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 에러 처리
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이었지만, 오늘도 강의노트와 필기한 것들을 긁어 오는 것에 그쳤고
그러면서 오늘도 역시 계속해서 내 우울함을 혼자 달래는 유일한 곳이 된 것 같다.
응원해주는 사람들이 많고 나도 열심히 하고자 하는 의욕이 가득한데,
현실을 크게 마주하니 그런 것들이 아무 소용이 없어지는 하루다.
잘 하고 있다라는 말로 나를 달래주는 것도 사치인 것 같아서
좀 많이 힘들다.