[JS] Node.js 핵심 개념 완전 정복 — 이벤트 루프부터 스트림까지
목차
서버와 클라이언트
- 서버: 네트워크를 통해 클라이언트에 정보나 서비스를 제공하는 컴퓨터 또는 프로그램.
- 요청에 대해 항상 Yes를 응답할 필요는 없다. No(차단)도 응답이다.
- 서버가 다른 서버에 요청을 보낼 때는 그 서버가 클라이언트 역할을 한다.
- 클라이언트: 요청을 보내는 주체. 브라우저, 데스크톱 앱, 모바일 앱, 또는 다른 서버가 될 수 있다.
Node.js란
Node.js는 Chrome V8 JavaScript 엔진으로 빌드된 JavaScript 런타임이다.
- 런타임: 특정 언어로 작성된 프로그램을 실행할 수 있는 환경.
- 노드 덕분에 JavaScript를 브라우저 밖, 즉 서버·터미널 환경에서도 실행할 수 있다.
- JavaScript를 전혀 모른다면 노드 사용이 불가능하다.
이벤트 기반(Event-Driven)
이벤트 기반이란 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식이다. 클릭, 네트워크 요청 등이 이벤트의 예다.
- 이벤트 발생 시 실행할 함수를 이벤트 리스너(event listener) 에 콜백(callback) 으로 등록해둔다.
- 이벤트 루프(event loop): 여러 이벤트가 동시에 발생했을 때 콜백 호출 순서를 결정하는 역할. 노드가 종료될 때까지 반복 동작한다.
호출 스택(Call Stack) 예제
function first() {
second();
console.log('첫 번째');
}
function second() {
third();
console.log('두 번째');
}
function third() {
console.log('세 번째');
}
first();
실행은 호출된 순서와 반대로 완료된다.
세 번째
두 번째
첫 번째
이벤트 루프·태스크 큐·백그라운드
function run() {
console.log('3초 후 실행');
}
console.log('시작');
setTimeout(run, 3000);
console.log('끝');
시작
끝
3초 후 실행
단순한 호출 스택만으로는 설명할 수 없는 이 순서를 이해하려면 세 가지 개념이 필요하다.
| 구성 요소 | 역할 |
|---|---|
| 이벤트 루프 | 콜백 실행 순서 결정, 노드 종료 시까지 반복 |
| 백그라운드 | setTimeout·이벤트 리스너 등이 대기하는 곳. 여러 작업 동시 실행 가능 |
| 태스크 큐 | 백그라운드에서 완료된 콜백들이 줄 서는 곳 (콜백 큐라고도 부름) |
논 블로킹 I/O
I/O(Input/Output): 파일 시스템 접근, 네트워크 요청 등.
- 블로킹(Blocking): 이전 작업이 끝나야만 다음 작업을 수행.
- 논 블로킹(Non-Blocking): 이전 작업 완료를 기다리지 않고 다음 작업을 수행.
논 블로킹 방식이 같은 작업을 더 짧은 시간에 처리할 수 있다(단, 작업들이 동시에 처리 가능한 I/O 작업이라는 전제가 있다).

블로킹 방식 예제
function longRunningTask() {
// 오래 걸리는 작업
console.log('작업 끝');
}
console.log('시작');
longRunningTask();
console.log('다음 작업');
시작
작업 끝
다음 작업
작업이 완료되기 전까지 다음 작업이 호출되지 않는다.
논 블로킹 예제
function longRunningTask() {
// 오래 걸리는 작업
console.log('작업 끝');
}
console.log('시작');
setTimeout(longRunningTask, 0);
console.log('다음 작업');
시작
다음 작업
작업 끝
setTimeout(콜백, 0)은 콜백을 태스크 큐로 보내므로 다음 작업이 먼저 실행된다.
참고 —
setTimeout(콜백, 0)이라도 즉시 실행되지 않는다. HTML5 브라우저는 최소 4ms, 노드는 최소 1ms의 지연이 있다.
노드에서는 동기 ≈ 블로킹, 비동기 ≈ 논 블로킹 으로 이해하면 된다.
싱글 스레드(Single Thread)
JavaScript 코드가 동시에 실행될 수 없는 이유는 노드가 싱글 스레드 기반이기 때문이다.
| 개념 | 설명 |
|---|---|
| 프로세스 | 운영체제가 할당하는 작업 단위. 프로세스끼리 메모리를 공유하지 않음 |
| 스레드 | 프로세스 내 실행 흐름의 단위. 같은 프로세스의 스레드끼리는 메모리 공유 가능 |

노드를 실행하면 내부적으로 스레드를 여러 개 생성하지만, 직접 제어 가능한 스레드는 하나다. 그래서 “싱글 스레드”라고 부른다.
블로킹이 심하지 않다면 스레드 하나로도 많은 I/O 요청을 처리할 수 있다. 블로킹이 발생할 것 같은 경우에는 논 블로킹 방법으로 대기 시간을 최대한 줄인다.
멀티 스레딩과 멀티 프로세싱 비교

멀티 스레드 방식으로 프로그래밍하는 것은 난이도가 높으므로, 노드에서는 멀티 프로세싱 방식을 주로 사용한다. I/O 요청에는 멀티 프로세싱이 더 효율적이기도 하다.
서버로서의 노드
노드는 기본적으로 싱글 스레드 + 논 블로킹 모델이다.
- I/O 처리에 강하다.
libuv라이브러리를 사용해 I/O 작업을 논 블로킹으로 처리한다. - CPU 연산이 많은 작업(이미지 처리, 머신러닝 등)에는 적합하지 않다.
- 실시간 채팅, 주식 차트, JSON API 서버 등 작은 데이터를 많이 주고받는 서비스에 적합하다.
- 웹 서버가 내장되어 있어 Apache·nginx·IIS 같은 별도 웹 서버 없이도 바로 사용 가능하다.

프로미스(Promise)
ES2015부터 콜백 대신 프로미스(Promise) 기반 API가 도입되어 콜백 지옥(callback hell) 문제를 해소했다.
const condition = true; // true면 resolve, false면 reject
const promise = new Promise((resolve, reject) => {
if (condition) {
resolve('성공');
} else {
reject('실패');
}
});
// 다른 코드가 들어갈 수 있음
promise
.then((message) => {
console.log(message); // 성공(resolve)한 경우 실행
})
.catch((error) => {
console.error(error); // 실패(reject)한 경우 실행
})
.finally(() => { // 끝나고 무조건 실행
console.log('무조건');
});
resolve호출 →.then()실행reject호출 →.catch()실행.finally()→ 성공·실패 무관하게 항상 실행
REPL
노드는 REPL(Read Eval Print Loop) 콘솔을 제공한다. 컴파일 없이 즉석에서 코드를 실행할 수 있다.
모듈 시스템
노드는 코드를 모듈로 나눌 수 있다. 모듈이란 특정 기능을 하는 함수나 변수들의 집합이다.
ES2015 모듈 (mjs)
노드 9 버전부터 ES2015 모듈 시스템을 사용할 수 있다. 파일 확장자를 .mjs로 지정하거나 package.json에 "type": "module"을 추가하면 된다.
// func.mjs
import { odd, even } from './var';
function checkOddOrEven(num) {
if (num % 2) { // 홀수면
return odd;
}
return even;
}
export default checkOddOrEven;
require / module.exports → import / export default 로 대응된다.
노드 내장 객체
global
브라우저의 window에 해당하는 전역 객체. 모든 파일에서 접근 가능하며, global.require처럼 생략해서 사용할 수 있다.
console
디버깅용 객체. 변수 확인, 에러 출력, 실행 시간 측정 등에 사용한다. 대표 메서드는 console.log.
타이머
| 함수 | 설명 |
|---|---|
setTimeout(콜백, ms) | 지정한 밀리초 후 콜백 실행 |
setInterval(콜백, ms) | 지정한 밀리초마다 콜백 반복 실행 |
setImmediate(콜백) | 콜백 즉시 실행 |
clearTimeout(id) | setTimeout 취소 |
clearInterval(id) | setInterval 취소 |
clearImmediate(id) | setImmediate 취소 |
__filename, __dirname
모듈 관계가 있을 때 현재 파일의 경로·파일명을 알아야 하는 경우에 사용한다.
module, exports, require
module.exports와 exports는 같은 객체를 참조한다(console.log(module.exports === exports) → true).

exports에는 반드시 속성명과 속성값 형태로 대입해야 한다. 다른 값을 대입하면 참조가 끊긴다.- 한 모듈에서
exports와module.exports를 동시에 사용하지 않는 것이 좋다.
노드에서
this란? 최상위 스코프의this는module.exports를 가리키고, 함수 선언문 내부의this는global을 가리킨다.
process
현재 실행 중인 노드 프로세스 정보를 담는 객체.
$ node
> process.version
v14.0.0 // 설치된 노드의 버전입니다.
> process.arch
x64 // 프로세서 아키텍처 정보입니다. arm, ia32 등의 값일 수도 있습니다.
> process.platform
win32 // 운영체제 플랫폼 정보입니다. linux나 darwin, freebsd등의 값일 수도 있습니다.
> process.pid
14736 // 현재 프로세스의 아이디입니다. 프로세스를 여러 개 가질 때 구분할 수 있습니다.
> process.uptime()
199.36 // 프로세스가 시작된 후 흐른 시간입니다. 단위는 초입니다.
> process.execPath
C:\\Program Files\\nodejs\\node.exe // 노드의 경로입니다.
> process.cwd()
C:\\Users\\zerocho // 현재 프로세스가 실행되는 위치입니다.
> process.cpuUsage()
{ user: 390000, system: 203000 } // 현재 cpu 사용량입니다.
process.env
시스템 환경 변수를 담은 객체. UV_THREADPOOL_SIZE, NODE_OPTIONS 등 노드 동작에 직접 영향을 주는 변수도 있다.
process.nextTick(콜백)

노드 내장 모듈
os
운영체제 정보를 가져오는 모듈.
• os.arch(): process.arch와 동일합니다.
• os.platform(): process.platform과 동일합니다.
• os.type(): 운영체제의 종류를 보여줍니다.
• os.uptime(): 운영체제 부팅 이후 흐른 시간(초)을 보여줍니다. process.uptime()은 노드의 실행 시간이었습니다.
• os.hostname(): 컴퓨터의 이름을 보여줍니다.
• os.release(): 운영체제의 버전을 보여줍니다.
• os.homedir(): 홈 디렉터리 경로를 보여줍니다.
• os.tmpdir(): 임시 파일 저장 경로를 보여줍니다.
• os.cpus(): 컴퓨터의 코어 정보를 보여줍니다.
• os.freemem(): 사용 가능한 메모리(RAM)를 보여줍니다.
• os.totalmem(): 전체 메모리 용량을 보여줍니다.
path
폴더와 파일의 경로를 조작하는 모듈. 윈도(Windows)와 POSIX(macOS·Linux)의 경로 구분자가 다르기 때문에 필요하다.
• path.sep: 경로의 구분자입니다. 윈도는 \, POSIX는 /입니다.
• path.delimiter: 환경 변수의 구분자입니다. process.env.PATH를 입력하면 여러 개의 경로가 이 구분자로 구분되어 있습니다. 윈도는 세미콜론(;)이고, POSIX는 콜론(:)입니다.
• path.dirname(경로): 파일이 위치한 폴더 경로를 보여줍니다.
• path.extname(경로): 파일의 확장자를 보여줍니다.
• path.basename(경로, 확장자): 파일의 이름(확장자 포함)을 표시합니다. 파일의 이름만 표시하고 싶다면 basename의 두 번째 인수로 파일의 확장자를 넣으면 됩니다.
• path.parse(경로): 파일 경로를 root, dir, base, ext, name으로 분리합니다.
• path.format(객체): path.parse()한 객체를 파일 경로로 합칩니다.
• path.normalize(경로): /나 \를 실수로 여러 번 사용했거나 혼용했을 때 정상적인 경로로 변환합니다.
• path.isAbsolute(경로): 파일의 경로가 절대경로인지 상대경로인지를 true나 false로 알립니다.
• path.relative(기준경로, 비교경로): 경로를 두 개 넣으면 첫 번째 경로에서 두 번째 경로로 가는 방법을 알립니다.
• path.join(경로, …): 여러 인수를 넣으면 하나의 경로로 합칩니다. 상대경로인 ..(부모 디렉터리)과 .(현 위치)도 알아서 처리합니다.
• path.resolve(경로, …): path.join()과 비슷하지만 차이가 있습니다. 차이점은 다음에 나오는 Note에서 설명합니다.
path.join vs path.resolve

/를 만나면 path.resolve는 절대경로로 인식해 앞의 경로를 무시하고, path.join은 상대경로로 처리한다.
\\vs\— 자바스크립트 문자열에서\는 특수 문자이므로 실제 경로에는\\를 써야 한다.path모듈은 이를 자동으로 처리해준다.
절대경로와 상대경로
- 절대경로: 루트 폴더(
C:\또는/)가 기준. - 상대경로: 현재 파일이 기준.
.은 현재 위치,..는 한 단계 위.
url
인터넷 주소를 조작하는 모듈. WHATWG 방식과 기존 노드 방식 두 가지를 지원한다.
// url.js
const url = require('url');
const { URL } = url;
const myURL = new URL('http://www.gilbut.co.kr/book/bookList.aspx?sercate1=001001000#anchor');
console.log('new URL():', myURL);
console.log('url.format():', url.format(myURL));
console.log('------------------------------');
const parsedUrl = url.parse('http://www.gilbut.co.kr/book/bookList.aspx?sercate1=001001000#anchor');
console.log('url.parse():', parsedUrl);
console.log('url.format():', url.format(parsedUrl));
url 모듈 안에 URL 생성자가 있습니다. 이 생성자에 주소를 넣어 객체로 만들면 주소가 부분별로 정리됩니다. 이 방식이 WHATWG의 url입니다. WHATWG에만 있는 username, password, origin, searchParams 속성이 존재합니다.
기존 노드 방식의 주요 메서드:
url.parse(주소): 주소를 분해한다. WHATWG의username/password대신auth,searchParams대신query속성을 사용한다.url.format(객체): 분해된 url 객체를 다시 원래 문자열로 조립한다.
host 없이 pathname만 오는 주소(
/book/bookList.aspx)는 WHATWG 방식으로 처리할 수 없다. 이 경우 기존 노드 방식을 사용해야 한다.
searchParams 주요 메서드
| 메서드 | 설명 |
|---|---|
getAll(키) | 키에 해당하는 모든 값 반환 |
get(키) | 키에 해당하는 첫 번째 값만 반환 |
has(키) | 해당 키 존재 여부 확인 |
keys() | 모든 키를 반복기 객체로 반환 |
values() | 모든 값을 반복기 객체로 반환 |
append(키, 값) | 기존 값을 유지하며 추가 |
set(키, 값) | 기존 값을 지우고 새로 추가 |
delete(키) | 해당 키 제거 |
toString() | searchParams 객체를 문자열로 변환 |
querystring
기존 노드 url 방식 사용 시, query 문자열을 객체로 변환하는 모듈.
// querystring.js
const url = require('url');
const querystring = require('querystring');
const parsedUrl = url.parse('http://www.gilbut.co.kr/?page=3&limit=10&category=nodejs&category=javascript');
const query = querystring.parse(parsedUrl.query);
console.log('querystring.parse():', query);
console.log('querystring.stringify():', querystring.stringify(query));
querystring.parse(쿼리): url의 query 부분을 JavaScript 객체로 분해.querystring.stringify(객체): 분해된 query 객체를 문자열로 재조립.
crypto
다양한 암호화를 지원하는 모듈. 멀티 스레드로 동작한다.
단방향 암호화 (해시)
비밀번호는 복호화할 수 없는 단방향 암호화(해시)를 사용한다. 로그인 시에는 입력값을 동일한 알고리즘으로 해시화해 DB의 값과 비교한다.
| 메서드 | 설명 |
|---|---|
createHash(알고리즘) | 해시 알고리즘 지정. sha512 권장 (md5·sha1은 취약점 발견됨) |
update(문자열) | 변환할 문자열 입력 |
digest(인코딩) | 인코딩 후 결과 반환. base64가 결과 문자열이 가장 짧아 애용됨 |

현재는 pbkdf2, bcrypt, scrypt 알고리즘이 주로 사용된다. 노드에서 지원하는 pbkdf2는 기존 문자열에 salt 문자열을 붙인 후 해시 알고리즘을 반복 적용한다.

양방향 암호화 (대칭형)
암호화된 문자열을 복호화할 수 있으며, 암호화·복호화 시 같은 키(key) 를 사용한다.
| 메서드 | 설명 |
|---|---|
crypto.createCipheriv(알고리즘, 키, iv) | 암호화 객체 생성. aes-256-cbc 사용 시 키 32바이트·iv 16바이트 |
cipher.update(문자열, 인코딩, 출력인코딩) | 암호화 대상 입력. 보통 utf8 → base64 |
cipher.final(출력인코딩) | 암호화 완료 |
crypto.createDecipheriv(알고리즘, 키, iv) | 복호화 객체 생성. 암호화 때와 동일한 알고리즘·키·iv 사용 |
decipher.update(문자열, 인코딩, 출력인코딩) | 복호화 입력. base64 → utf8 순서로 |
decipher.final(출력인코딩) | 복호화 완료 |
util
각종 편의 기능을 모아둔 모듈.
util.deprecate: 함수가 deprecated 처리되었음을 알리는 경고 메시지 출력.util.promisify: 콜백 패턴을 프로미스 패턴으로 변환.async/await까지 활용 가능.
worker_threads
노드에서 멀티 스레드 방식으로 작업하는 모듈. CPU 집약적 연산이나 압축 작업 등 특수한 경우에 사용한다. 대부분의 노드 서비스는 싱글 스레드로 충분하다.


const {
Worker, isMainThread, parentPort,
} = require('worker_threads');
if (isMainThread) { // 부모일 때
const
worker = new Worker(__filename);
worker.on('message', message => console.log('from worker', message));
worker.on('exit', () => console.log('worker exit'));
worker.postMessage('ping');
} else { // 워커일 때
parentPort.on('message', (value) => {
console.log('from parent', value);
parentPort.postMessage('pong');
parentPort.close();
});
}
- 메인 스레드에서 워커 스레드를 생성하고 메시지를 보낸다.
- 워커 스레드는 작업 완료 후 결과를 부모로 전달하고 종료된다.
- 워커 종료 판단은 자동이 아니므로 수동으로 관리해야 한다.
child_process
다른 언어(파이썬 등)의 코드를 실행하거나 명령어를 수행할 때 사용하는 모듈. 현재 노드 프로세스 외에 자식 프로세스를 새로 띄워서 결과를 받는다.
spawn 의 첫 번째 인수로 명령어를, 두 번째 인수로 옵션 배열을 넣으면 된다. 결과는 stdout, stderr의 데이터로 나온다.
기타 내장 모듈
• assert: 값을 비교하여 프로그램이 제대로 동작하는지 테스트하는 데 사용합니다.
• dns: 도메인 이름에 대한 IP 주소를 얻어내는 데 사용합니다.
• net: HTTP보다 로우 레벨인 TCP나 IPC 통신을 할 때 사용합니다.
• string_decoder: 버퍼 데이터를 문자열로 바꾸는 데 사용합니다.
• tls: TLS와 SSL에 관련된 작업을 할 때 사용합니다.
• tty: 터미널과 관련된 작업을 할 때 사용합니다.
• dgram: UDP와 관련된 작업을 할 때 사용합니다.
• v8: V8 엔진에 직접 접근할 때 사용합니다.
• vm: 가상 머신에 직접 접근할 때 사용합니다.
파일 시스템(fs 모듈)
파일·폴더를 생성·삭제·읽기·쓰기할 수 있는 모듈. 브라우저 JavaScript와 달리 노드는 파일 시스템에 자유롭게 접근할 수 있다.
const fs = require('fs');
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log(data);
console.log(data.toString());
});
실무에서는 프로미스 형식을 주로 사용한다.
const fs = require('fs').promises;
fs.readFile('./readme.txt')
.then((data) => {
console.log(data);
console.log(data.toString());
})
.catch((err) => {
console.error(err);
});
경로 주의 —
readFile의 파일 경로는 현재 파일 기준이 아니라node명령어를 실행하는 콘솔 기준이다.
동기와 비동기, 블로킹과 논 블로킹
| 구분 | 설명 |
|---|---|
| 동기 vs 비동기 | 백그라운드 작업 완료를 확인하는지 여부 |
| 블로킹 vs 논 블로킹 | 함수가 바로 return되는지 여부 |
노드에서는 동기-블로킹과 비동기-논 블로킹 두 조합이 대부분이다.
- 동기-블로킹: 백그라운드 완료를 계속 확인하며, 완료되어야 return.
- 비동기-논 블로킹: 함수가 바로 return되어 다음 작업으로 넘어가고, 백그라운드 완료 알림을 나중에 받아 처리.

버퍼(Buffer)와 스트림(Stream)
버퍼
노드는 파일을 읽을 때 메모리에 파일 크기만큼 버퍼를 만들어 데이터를 저장한다.

Buffer 주요 메서드:
| 메서드 | 설명 |
|---|---|
from(문자열) | 문자열을 버퍼로 변환 |
toString(버퍼) | 버퍼를 문자열로 변환. base64·hex 인코딩도 가능 |
concat(배열) | 배열 안의 버퍼들을 하나로 합침 |
alloc(바이트) | 지정한 크기의 빈 버퍼 생성 |
버퍼의 단점: 용량이 큰 파일(예: 100MB)을 동시에 여러 명이 읽으면 메모리가 급격히 증가한다. 이를 해결하는 것이 스트림이다.
스트림
버퍼 크기를 작게 나눠 여러 번 전송하는 방식. 메모리 1MB로 100MB 파일을 전송할 수 있다.

createReadStream으로 읽기 스트림을 만들고 data, end, error 이벤트를 붙여 사용한다.
readme.txt를 16바이트씩 읽어 writeme.txt에 16바이트씩 쓰는 파이프 연결 방식이 기본 패턴이다. gzip 방식으로 압축하면서 쓰는 것도 가능하다.



fs 주요 메서드 정리
| 메서드 | 설명 |
|---|---|
fs.access(경로, 옵션, 콜백) | 폴더·파일 접근 가능 여부 체크. F_OK(존재), R_OK(읽기), W_OK(쓰기) |
fs.mkdir(경로, 콜백) | 폴더 생성. 이미 존재하면 에러 |
fs.open(경로, 옵션, 콜백) | 파일 ID(fd) 취득. w(쓰기), r(읽기), a(추가) |
fs.rename(기존, 새경로, 콜백) | 파일 이름 변경 또는 이동(잘라내기) |
fs.readdir(경로, 콜백) | 폴더 내부 목록 확인 |
fs.unlink(경로, 콜백) | 파일 삭제. 없으면 에러 |
fs.rmdir(경로, 콜백) | 폴더 삭제. 내부에 파일이 있으면 에러 |
스레드풀(Thread Pool)
fs, crypto, zlib 같은 모듈은 백그라운드에서 동시에 실행된다. 기본 스레드풀 크기는 4개다.
- 처음 4개 작업이 동시에 실행되고, 완료되면 다음 4개가 실행된다.
- 컴퓨터 코어 수에 맞게
UV_THREADPOOL_SIZE환경 변수로 조정 가능하다.
이벤트 (커스텀 이벤트)
이벤트 패턴으로 다양한 동작을 구현할 수 있다.
on(이벤트명, 콜백)(addListener의 별칭): 이벤트 리스너 등록.once(이벤트명, 콜백): 이벤트가 발생하면 한 번만 실행.removeListener(이벤트명, 콜백)/removeAllListeners(이벤트명): 이벤트 제거.
npm과 주요 패키지
npm(Node Package Manager): 터미널에서 패키지를 설치하고 관리하는 도구.
| 패키지 | 용도 |
|---|---|
| Babel | 최신 JS 문법을 구형 브라우저에서도 동작하도록 트랜스파일 |
| nodemon | 파일 변경 시 서버 자동 재시작 |
| socket.io | 실시간 웹소켓 통신 |
| Axios | HTTP 요청 라이브러리 (Promise 기반) |
| Express | 노드 웹 프레임워크 |
| electron | 데스크톱 앱 개발 |
| helmet | Express 보안 미들웨어 |
Express 미들웨어 개념
미들웨어는 요청(request)을 받아 다음 처리로 연결하는 함수들의 체인이다. C의 함수 포인터처럼 next로 다음 미들웨어를 호출한다.
body-parser
Express에서 JSON 형식의 요청 본문(body)을 파싱한다.
cookie-parser
쿠키 문자열을 파싱해 객체로 만든다.
- 쿠키: 클라이언트(브라우저)에 저장되는 데이터. 로그인 상태 유지 등에 사용.
- 세션: 답지(세션 데이터)를 서버에 저장. 서버 부하가 있으나 클라이언트에 민감 정보를 노출하지 않음.
- JWT(JSON Web Token): 헤더·바디·시그니처로 구성. 바디가 하나라도 바뀌면 시그니처가 달라져 조작 여부를 판단 가능. 서버에 상태를 저장하지 않으므로 서버 다중화(클러스터)가 불필요.