얄코님의 '얄코의 Node.js (Korean ver.)' 강의를 듣고 정리한 내용입니다.
얄코의 Node.js (Korean ver.) 강의 | 얄팍한 코딩사전 - 인프런
얄팍한 코딩사전 | , 🇰🇷 This course is designed for Korean-speaking learners. If you speak English, Japanese, Vietnamese, or any other language, please take t
www.inflearn.com
1. 파일 시스템
CommonJS에서 파일 시스템
fs로 파일을 읽을 수 있다.
const fs = require('fs');
fs.readFile('파일 경로', '데이터를 읽는 데 사용할 형식(utf8)', '콜백 함수')
동기적으로 불러오고 싶으면 readFile 대신 readFileSync를 사용하면 된다.
파일을 쓰려면 writeFile(동기적으로 하고 싶으면 writeFileSync)를 활용하면 된다.
데이터를 이어붙이고 싶으면 appendFile(동기적으로 하고 싶으면 appendFileSync)를 활용하면 된다.
특정 파일의 유무를 확인하고 싶으면 access를 (동기적으로 하고 싶으면 existsSync - 파일 유무에 따라 boolean 값 반환) 사용하면 된다. (exists는 deprecated)
→ 원래 access는 파일이나 디렉토리에 특정 권한이 있는지 확인하는 함수
→ 유무를 확인하려면 access의 두 번째 인자에 fs.constants.F_OK를 넣으면 된다
파일을 지우고 싶으면 unlink(동기적으로 하고 싶으면 unlinkSync)를 활용하면 된다.
→ 파일이 없으면 오류 발생
__dirname: 실행되고 있는 문서의 디렉토리 경로
__filename: 해당 문서의 파일명을 포함한 경로
path 모듈
join: 인자로 주어진 경로 조각들을 이어붙여서 경로로 만들어줌
resolve: 작업중인 디렉토리에 인자로 주어진 경로를 이어붙여서 절대 경로 생성 (해당 프로세스를 실행한 위치 기준)
basename: 해당 경로의 마지막에 있는 파일이나 폴더의 이름을 추출하여 반환
dirname: 디렉토리의 경로 반환
extname: 파일의 확장자를 추출하여 반환
parse: 해당 경로의 정보를 담은 객체 반환
format: parse 함수로 만든 객체를 format 함수에 객체로 넣어주면 해당 경로의 문자열로 조합해서 반환
relative: 첫 번째 인자(경로)의 파일에서 두번째 인자(경로)로 어떻게 이동해야되는지 알려줌
→ 같은 드라이브에 위치하고 같은 기준점에서 시작해야한다.
ES Module에서 파일 시스템
구조 분해 할당으로 fs로부터 필요한 함수만 가져와 사용할 수 있다.
import { readFileSync, readFile } from ‘fs’;
→ 하지만 선호되지는 않음
→ fs 대신 fs/promises(fs의 비동기 모듈들을 프로미스 기반으로 래핑한 모듈)가 선호됨
→ ES Module에서는 최상위 스코프에서 await가 사용 가능하므로 동기 작업처럼 작성 가능
ES Module에서는 filename과 dirname이 전역 변수로 제공되지 않는다
→ fileURLToPath(import.meta.url)로 알아낼 수 있음
readdir: 폴더 안의 들어있는 것들의 목록을 가져올 때 사용
stat: 해당 파일에 대한 자세한 정보를 객체로 얻을 수 있음
mkdir: 디렉토리를 만들 수 있음 (하위 폴더까지 한 번에 생성하려면 두 번째 인자에 { recursive: true}를 주면 된다
rename: 첫 번째 인자로 주어진 경로의 파일(폴더)를 찾아서 경로를 두 번째 인자로 주어진 것으로 변경한다
copyFile: 파일을 복사할 때 사용된다
rmdir: 빈 디렉토리만 삭제 가능
rm: 내용이 있는 디렉토리도 삭제 가능
chmod: 파일이나 폴더의 권한 변경
truncate: 파일의 크기를 특정 값으로 줄이는데 사용
utimes: 파일의 접근 시간과 변경 시간을 변경
파일 시스템 이벤트
readline: 프로그램 실행 중 터미널로부터 사용자의 키보드 입력을 받아오는데 사용되는 도구
import readline from ‘readline/promises’
‘process’로부터 현지 실행 되고 있는 프로그램의 표준 입력 스트림, 출력 스트림
→ import { stdin as input, stdout as output } from ‘process’;
readline의 createInterface 함수를 활용하여 input과 output을 연결
const rl = readline.createInterface({ input, output )};
→ 질문을 출력하고 사용자의 입력을 받고 싶을 때는 rl.question 사용
→ 다 쓰고 나서는 rl.close(); 입력
watch: 파일이나 폴더의 상태에 변화가 있을 시 OS에서 보내주는 신호를 기다리고, 신호가 오면 그에 반응
폴더에서 일어나는 이벤트에 콜백 함수가 실행되어 터미널에 로그가 출력
→ 각 로그에는 이벤트가 발생한 파일 및 폴더의 이름과 이벤트의 종류가 출력
→ 파일 추가, 이동, 삭제, 이름 변경의 이벤트 타입: rename
→ 파일의 내용이 수정되어 저장될 때 이벤트 타입: change
watchFile: 일정 간격을 두고 확인 (폴링 방식)
시간 간격에 대한 옵션을 넣지 않으면 5007ms(소수)로 파일을 확인
감시를 종료하고 싶으면 unwatchFile 함수를 사용하자
watch와 watchFile은 불완전하므로 일반적으로는 Chokidar 같은 외부 라이브러리를 사용
2. TCP/UDP
TCP: 보낸 데이터가 확실히 도착하도록 하는 것을 속도보다 우선시하는 방식
→ 먼저 보낸 데이터 조각이 제대로 도착한 것을 확인한 후, 다음 조각을 보냄
→ 웹 페이지 로딩, 파일 전송, 이메일 등에서 사용
TCP를 구현할 때에는 내장 모듈인 net 모듈 사용
import net from ‘node:net’; (node: prefix가 있으면 혼동하지 않고 내장 net 모듈을 불러옴)
서버는 net의 createServer 함수로, 클라이언트는 createConnection 함수로 인스턴스를 생성해서 사용 (서버는 socket 인스턴스, 클라이언트는 clinent 인스턴스)
두 인스턴스 모두 on 메소드에서 ‘end’ 문자열에 할당한 콜백으로 연결 종료시 실행할 작업 지정
UDP: 속도를 우선시하는 방식
→ 보내어지는 데이터 조각의 유실 여부와 관계없이 계속해서 데이터를 전송
→ 영상 스트리밍, 온라인 게임 등에서 사용
UDP를 구현할 때에는 내장 모듈인 dgram 모듈 활용
UDP에서는 서버와 클라이언트의 개념이 명확히 나뉘어지지 않음
→ 둘 다 createSocket으로 인스턴스를 생성
const client = dgram.createSocket(’udp4’)
→ udp4: IPv4 기반의 UDP 소켓 생성
bind를 호출해서 패킷을 받을 준비를 하는 쪽이 서버
→ server.bind(PORT , () ⇒ { …
클라이언트에서는 소켓 인스턴스의 send 메소드로 서버에 데이터 전송
서버와 클라이언트 모두 소켓 인스턴스의 on 메시지를 활용하여 메시지를 받음
실무에서는 socket.io, ws, gRPC, MQTT같은 라이브러리 사용
3. HTTP
Express, NestJS, Fastify와 같은 프레임워크의 기반이 HTTP 모듈
createServer: HTTP 방식으로 동작하는 서버를 만드는 함수
→ 함수의 실행결과로 서버의 인스턴스가 만들어져서 server 함수에 할당
→ 함수의 매개변수에는 들어온 요청에 대한 정보를 담는 request 객체와 서버의 응답을 제어하는데 사용되는 response 객체가 있다.
→ 서버 인스턴스를 listen 메서드로 실행한다(인자로는 포트 번호, 시작시 호출될 콜백 함수가 들어감)
writeHead: 응답의 상태 코드와 헤더를 인자로 넣어 설정
→ 헤더에는 Content-type을 넣음(서버가 보낼 응답이 어떤 형식의 데이터가 될지 클라이언트에게 알려주는 것)
end: 응답을 종료하는 메서드
Node.js에서는 URL을 모듈로서 로드해야 한다.
→ 파일 시스템 경로로 직접 접근 가능
import http from "http";
import { URL } from "url";
const server = http.createServer((req, res) => {
const { searchParams } = new URL(req.url, `http://${req.headers.host}`);
const name = searchParams.get("name") || "";
(...)
request 객체로부터 URL과 host 정보를 추출해서 URL 모듈의 생성자에 넣어줌
→ 생성된 인스턴스로부터 구조 분해 할당으로 ‘searchParams’ 항목을 추출
→ get으로 name 키에 해당하는 값을 가져옴
HTTP 모듈로 req.method와 req.url을 조합하여 RESTful API를 제공할 수 있다.
import http from "http";
const server = http.createServer((req, res) => {
if (req.method === "GET" && req.url === "/") {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Welcome~");
}
else if (req.method === "POST" && req.url === "/submit") {
res.writeHEAD(200, { "Content-Type": "text/plain" });
res.end("Form Submitted");
else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("404 Not Found);
});
400번대 오류: 요청 자체에 문제가 있음
500번대 오류: 서버쪽의 문제에 의한 오류
201: 새로운 resource 생성
HTTP는 클라이언트가 되어 요청을 보내는데도 사용할 수 있다.
서버쪽에서 브라우저가 쿠키로 저장할 데이터를 전송할 수 있다.
서버는 request로부터 URL 객체를 추출해낸 다음 key와 value의 값을 가져옴
Set-Cookie 항목을 통해 브라우저가 어떤 키와 값을 쿠키로 저장할지 알려줌
if (path === "/set-cookie && key && value) {
res.writeHead(200, {
"Set-Cookie": `${key}=${value}; HttpOnly`,
"Content-Type": "text/plain" });
return res.end(`Cookie set: ${key}=${value}`);
}
요청으로부터 쿠키 데이터를 추출할 수 있다.
if (path === "/get-cookie" && key) {
const cookies = parseCookies(req.headers.cookie || "");
return res.end(cookies[key] ? `Value: ${cookes[key]` : "Not Found");
}
실무에서 서비스를 배포하려면 HTTPS를 사용해야함
→ HTTPS 모듈을 불러와서 사용하면 됨
→ 서버를 생성할 때 “key”와 “cert” 옵션으로 키와 인증서의 파일을 불러와 넣어주면 된다
→ 테스트 환경에서는 직접 생성해서 사용할 수 있지만 배포를 할 때에는 공인 CA 기관으로부터 받아와야 한다
REST API 참고할점
HATEOAS (Hypermedia As The Engine Of Application State)
→ 각 요청의 응답에, 가용한 다른 요청들의 정보를 포함
→ 이 리소스에 관해 이런 기능들도 요청할 수 있다고 첨부하는 것
Stateless
→ 클라이언트의 상태 정보가 서버에 저장되지 말아야 한다.
→ 클라이언트의 요청이 얼마나 반복되든 필요한 모든 내용을 포함하고 있어야 한다. (멱등성)
Cacheability
→ 자기가 어떤 응답을 보냈는지, 자기가 어떤 응답을 받았는지는 기억해두는게 좋다.
4. 버퍼와 스트림
스트림(Stream)
큰 데이터를 한 번에 옮기지 않고, 작은 조각으로 나누어 물 흐르듯이 다루는 기술
버퍼(Buffer)
뜻1: 옮겨지는 데이터 조각들이 일괄 처리를 위해 한 곳에 쌓이는 것
뜻2: 바이너리 데이터를 다루기 위한 객체
Buffer는 전역으로 제공되는 클래스
// buffer에는 'Hello, Node.js'를 버퍼 형태로 바꾼 값이 담김
const buffer = Buffer.from('Hello, Node.js');
// 문자열의 각 문자가 16진수로 표현되어 나타남
console.log(buffer);
// 다시 문자열로 나타남
console.log(buffer.toString();
// 인자로 주어진 크기만큼의 버퍼 인스턴스 생성
// 00 00 00 00 ...
buffer = Buffer.alloc(10);
// 인자로 들어온 값을 분해
// 41 42 43 44 00 00 ...
buffer.write('ABCD');
Buffer 인스턴스의 copy를 사용하면 인자로 주어진 다른 인스턴스로 데이터 복사 가능
ex) bufA.copy(bufB);
→ 문자열을 복사하는 것보다 빠르게 동작
이어붙이려면 concat을 사용하면 된다.
ex) const bufZ = Buffer.concat([bufX, bufY]);
request의 on 메서드에서 콜백함수에 매개변수로 주어지는 데이터는 버퍼 인스턴스
req.on('data', (chunk) => {
body += chunk;
console.log(chunk);
});
// 개선 버전
let body = [];
req.on('data', (chunk) => body.push(chunk));
req.on('end', () => {
const data = Buffer.concat(body).toString();
console.log('Received:', JSON.parse(data));
res.end('Data received')
});
이미지 파일을 문자열로 변환할 때 버퍼를 사용한다.
import { readFile} from 'fs/promises';
const filePath = '경로';
const buffer = await readFile(filePath);
const base64 = buffer.toString('base64');
fs/promises의 readFile 함수를 인코딩 옵션없이 사용하면 버퍼 형태로 파일 로드
base64 문자열은 어떤 시스템에서든 데이터가 깨지지 않음
버퍼는 바이너리 데이터를 안전하게 다룰 수 있는 객체
다시 이미지로 만드려면 writeFile을 쓰면 된다.
import { writeFile } from 'fs/promises';
const decodedBuffer = Buffer.from(base64, 'base64');
await writeFile('decoded.png', decodedBuffer);
Stream 모듈
Readable: 파일이나 서버 등으로부터 데이터를 읽어올 때 사용
import { Readable } from 'stream'
const readableStream = new Readable({
read() { // 스트림이 읽히기 시작할 때 실행할 작업 작성
this.push('Hello, '); // push로 스트림에 새 데이터를 추가
(...)
this.push(null); // 이 코드 없으면 스트림이 종료되지 않음
}
});
// on 메소드로 'data' 이벤트의 콜백을 등록하면 스트림 실행
readableStream.on('data', chunk => {
(...)
});
// 스트림이 종료되었을 때 실행
readableStream.on('end', () => {
(...)
});
커스텀 클래스를 활용해서 스트림 모듈 사용 가능
createReadStream: 파일 읽기 전용 스트림 함수 (fs 모듈에 내장)
→ encoding 옵션으로 데이터를 UTF-8 형식의 문자열로 읽게 할 수 있음
Writable: 데이터를 쓸 때 사용
const writableStream = new Writable({
write(chunk, encoding, callback) {
(...)
callback();
}
});
writableStream.write('Hello, ');
(...)
writablestream.end();
createWriteStream: 파일 쓰기 전용 스트림 함수 (fs 모듈에 내장)
const writeStream = createWriteStream('*.txt');
pipe 메소드를 사용해서 읽기 스트림과 쓰기 스트림 연결 가능
readStream.pipe(writeStream);
Duplex: 소켓과 같이 읽기와 쓰기가 모두 가능
readable의 요소, writable의 요소 둘 다 있음
예시: net 모듈의 소켓
Transform: 데이터를 읽은 뒤 변환해서 내보냄
import { Transform } from 'stream'
// 전달된 문자열을 모두 대문자로 바꾸어 내보냄
const upperCaseTransform = new Transform({
transform(chunk, encdoing, callback) {
callbacknull, chunk.toString().toUpperCase());
}
});
Transform에 Duplex의 확장이라 데이터를 쓰는 메소드와 전달된 데이터를 처리하는 메소드 모두 있음
‘zlib’ 모듈의 createGzip 함수: 데이터를 Gzip 형태로 압축하여 내보내는 Transform 스트림을 생성
crypto: Node.js에서 암호화 및 해시 기능을 제공하는 내장 모듈
5. 각종 모듈
a. url 모듈
url 모듈은 URL 클래스의 인스턴스를 생성해서 기능들을 사용
const myUrl = new URL('<https://example.com:8080/lecture?name=John&age=30#section1>');
console.log(myUrl.protocol); // https:
console.log(myUrl.hostname); // example.com
console.log(myUrl.origin); // <https://example.com>
console.log(myUrl.port); // 8080
console.log(myUrl.pathname) // /lecture
console.log(myUrl.searchParams.get('name')); // John
console.log(myUrl.hash); // #section1
console.log(myUrl.toString()); // <https://example.com:8080/lecture?name=John&age=30#section1>
searchParams
set: 매개변수 변경, 매개변수 추가
delete: 매개변수 삭제
append: 매개변수 추가(특정 매개변수가 두개 이상의 값을 갖게하려면)
get: 매개변수 값을 얻을 수 있음
getAll: 특정 매개변수의 모든 값들을 배열로 얻을 수 있음
has: URL에 특정 매개변수가 포함되어있는지 확인 (boolean)
url 모듈을 사용할 때 상대 경로들을 조합할 수 있다.
→ 상위 디렉토리의 이동을 기본 URL 경로 단계보다 많이했을 때에는 기본 URL로 처리
url 모듈의 fileURLToPath: 파일 URL을 파일 경로로 바꾸어줌
url 모듈의 pathToFileURL: 파일 경로를 파일 URL로 바꾸어줌
파일 URL: 웹 브라우저나 Node.js같은 환경에서 로컬 파일 시스템의 자원에 접근할 때 사용하는 URL 형식
파일 경로: 운영체제에서 파일이나 디렉토리의 위치를 나타내는 문자열
b. dns 모듈
도메인 이름 시스템과 관련된 작업을 수행하는데 사용되는 모듈
lookup: 주어진 도메인 이름을 실제 IP 주소로 변환하는 함수
resolve4: 도메인의 IPv4 주소, 즉 A 레코드를 조회해서 배열로 반환
resolve6: 도메인의 IPv6 주소, 즉 AAAA 레코드를 조회해서 배열로 반환
reverse: IP 주소를 도메인 이름으로 변환하는 역방향 DNS 조회를 수행하는 함수
resolveMx: 도메인의 메일 서버, 즉 MX 레코드를 조회해서 배열로 반환하는 함수(exchange는 메일 서버 도메인, priority는 우선순위)
c. util 모듈
promisify: 인자로 전달된 콜백 기반 함수를 프로미스 기반으로 변환
import { promisify } from 'node:util;
const sleep = promisify(setTimeout);
await sleep(2000);
inspect: 객체를 사람이 읽기 쉬운 문자열로 변환해줌(디버깅, 로깅에서 유용)
첫번째 인자로 대상 객체를, 두번째 인자로 옵션 객체를 넣어줌
color, depth, maxArrayLength, compact 등의 옵션으로 원하는 결과값을 확인할 수 있다.
type: typeof나 instanceof로 구분하기 어려운 특정 타입(Date의 인스턴스, 정규화 표현식, Map과 Set, 프로미스, 특정 종류의 함수들, 네이티브 오류 객체 등)을 정확히 판별할 때 유용
deprecate: 어떤 함수가 문제가 있거나 새 함수로 대체되어 앞으로 사용하지 않게 되는 상황이 생겼을 때, 개발자에게 경고 메시지 전달할 경우에 사용
d. os 모듈
운영 체제와 관련된 정보를 제공
platform: 현재 운영 체제의 플랫폼을 문자열로 반환
arch: CPU의 아키텍처를 문자열로 반환
type: 운영 체제의 유형을 문자열로 반환
release: 운영 체제의 버전을 문자열로 반환
cpus: 시스템의 CPU 코어 정보를 객체 배열로 반환
totalmem: 시스템의 총 메모리 크기를 반환
freemem: 현재 사용 가능한 메모리 크기를 반환
userInfo: 현재 사용자의 정보를 객체로 반환(userInfo().username → 사용자 이름)
homedir: 현재 사용자의 홈 디렉토리 경로를 문자열로 반환
tmpdir: 시스템의 기본 임시 디렉토리 경로(운영 체제가 임시 파일을 저장하는 데 사용하는 표준 폴더 경로)를 문자열로 반환
uptime: 시스템이 부팅된 이후 경과한 시간을 초 단위로 반환
hostname: 시스템의 호스트 이름을 문자열로 반환
networkInterfaces: 시스템의 네트워크 인터페이스 정보를 객체로 반환