Node.js API Guide 04 — 파일 저장하기 (todos.json) | 0xccffff Log
Article Node.js API Guide 04 — 파일 저장하기 (todos.json) Tutorials Feb 20, 2026 Views: 6 Updated: 2026-02-22 Tested: Node 24 LTS, Windows 11 /posts/node-api-04-persist
nodejs api express json filesystem persistence beginner
Related posts 미션 해설을 핑계로 PATCH/DELETE를 추가해 CRUD를 완성합니다. req.params(주소창의 숫자), done 토글, 삭제까지 구현하고 PowerShell로 직접 테스트합니다.
지금까지 만든 API 서버 구조를 한 번에 정리하고, 초보자가 꼭 기억해야 할 실전 감각 3가지와 제작 비하인드를 전하며 시리즈를 마무리합니다.
검증/에러처리/간단 인증을 추가해, 초보 단계에서 실전에서 자주 막히는 지점을 한 번에 정리합니다.
GET/POST 요청으로 TODO API를 만들고, 브라우저와 PowerShell에서 JSON 응답을 확인합니다.
04 — 파일 저장하기 (todos.json)#
이 글에서는 03편에서 만든 TODO를 메모리 가 아니라 파일(todos.json)에 저장 하도록 바꿉니다.
목표는 하나입니다: 서버를 껐다 켜도 TODO가 남아있게 만들기 .
Prerequisites
03편 완료 (GET/POST /todos가 동작하는 상태)
작업 폴더: C:\Workspace\node-api
Windows Terminal 기본 셸: PowerShell
0) 실습 준비 (이전 편과 동일)#
이전과 똑같이 서버를 켤 준비를 해줍니다.
기억이 잘 안 난다면 03. TODO API 만들기 를 먼저 보고 와도 좋습니다.
VS Code에서 폴더 열기: File > Open Folder... → C:\Workspace\node-api 선택
터미널 열기: VS Code 상단 Terminal > New Terminal
Tip
터미널에 pwd를 입력해서 현재 위치가 C:\Workspace\node-api가 맞는지 꼭 확인하세요.
Copypwd
1) 지금 문제: 서버를 끄면 TODO가 사라짐#
03편에서는 TODO를 todos 배열에 넣었습니다.
이 배열은 “서버 프로그램이 켜져 있는 동안만” 살아있습니다.
Note
그래서 서버를 껐다 켜면 배열이 초기화되고, TODO도 같이 사라집니다.
이번 편은 이걸 파일로 저장 해서 해결합니다.
2) 저장 파일 만들기 (todos.json)#
프로젝트 폴더(C:\Workspace\node-api)에 todos.json 파일을 하나 만들고, 아래처럼 빈 배열 을 넣고 저장합니다.
Copytodos.json []
Expected result
정상이라면 프로젝트 폴더에 todos.json 파일이 생겼고, 내용이 [] 입니다.
Warning
todos.json 파일을 만들지 않아도 코드에서 자동 생성할 수 있지만,
초보 단계에서는 파일이 “어디에 생기는지”가 헷갈릴 수 있습니다.
그래서 이번 편은 직접 파일을 만들어서 위치를 고정 합니다.
3) server.js 수정하기 (파일 읽기/쓰기 추가)#
이번 편에서 바뀌는 건 “파일 읽기/쓰기” 딱 한 가지입니다.
코드는 길어졌지만, 핵심은 몇 군데만 추가되는 거라서 추가되는 부분부터 보고, 맨 아래에서 전체 코드를 한 번에 확인합니다.
3-1) 파일을 다루는 부품 2개 추가 (fs, path)#
기존 server.js 맨 위에 fs, path를 붙입니다.
Copyconst express = require ( "express" );
// 👇 새로 추가된 부분: 파일을 읽고/쓰기 위한 기본 부품
const fs = require ( "fs" );
const path = require ( "path" );
3-2) todos.json 파일 위치 고정하기 (DATA_PATH)#
server.js와 같은 폴더의 todos.json을 항상 바라보게 경로를 하나 만들어 둡니다.
Copy// todos.json 파일 경로 (server.js와 같은 폴더)
const DATA_PATH = path. join (__dirname, "todos.json" );
__dirname은 “현재 server.js가 있는 폴더”입니다.
그래서 실행 위치가 바뀌어도 파일 경로가 안 꼬입니다.
3-3) 파일 저장/불러오기 함수 추가하기#
DATA_PATH 아래에 두 함수를 만듭니다. (아래 전체 코드에 그대로 들어 있습니다)
loadTodosFromFile() : 시작할 때 파일을 읽어서 배열로 만들기
saveTodosToFile(todos) : 배열을 파일에 덮어쓰기
Note
파일 저장은 결국 “텍스트 변환”입니다#
fs.readFileSync(...) : 파일을 텍스트 로 읽기
JSON.parse(...) : 텍스트(JSON) → JavaScript 값(배열/객체)
JSON.stringify(...) : JavaScript 값 → 텍스트(JSON)
fs.writeFileSync(...) : 텍스트를 파일에 쓰기
3-4) 시작할 때 파일을 읽어서 todos 배열 만들기#
03편에서는 메모리에서 시작했습니다.
let nextId = 1;
const todos = [];
이번 편은 시작할 때 파일에서 읽어옵니다.
Copylet todos = loadTodosFromFile ();
// 저장된 TODO 중 가장 큰 id를 찾아서, 그 다음 번호부터 시작합니다.
let nextId = 1 ;
for ( const t of todos) {
if ( typeof t.id === "number" && t.id >= nextId) {
nextId = t.id + 1 ;
}
}
3-5) POST /todos에서 저장 한 줄 추가#
todos.push(todo); 다음 줄에 이 한 줄이 들어갑니다.
CopysaveTodosToFile (todos);
Note
최종 확인용 전체 코드# 위 내용을 합친 server.js 전체입니다.
헷갈리면 아래 코드로 통째로 교체 하고 다음 단계로 넘어가도 됩니다.
Copyserver.js const express = require ( "express" );
const fs = require
Expected result
정상이라면 server.js에 fs, path, DATA_PATH, load/save 함수가 들어가 있고 저장까지 완료된 상태입니다.
Note
핵심은 3줄입니다.
시작할 때 파일에서 읽기: loadTodosFromFile()
TODO 추가 후 파일에 저장: saveTodosToFile(todos)
다음 id 계산: nextId를 “가장 큰 id + 1”로 맞추기
4) 서버 재시작 (중요)#
코드를 바꿨다면 서버를 재시작해야 적용됩니다.
Warning
중요: 코드를 수정했다면 서버를 껐다가 다시 켜야 반영됩니다.
서버가 켜진 터미널에서 Ctrl + C
다시 실행: node server.js
Copynode server.js
Expected result
정상이라면 Server running: http://localhost:3000가 출력됩니다.
Troubleshooting
SyntaxError: Unexpected token ... in JSON at position ...# 대부분 todos.json이 JSON 형식이 깨진 경우입니다.
체크:
todos.json이 정확히 [] 또는 [ ... ] 형태인지 확인합니다.
쉼표(,) 빠짐, 따옴표 오류가 있으면 JSON.parse가 실패합니다.
빠른 해결:
todos.json을 열고 내용 전체를 []로 바꾼 뒤 저장
서버 재시작
5) TODO 추가해서 파일이 갱신되는지 확인 (POST)#
서버를 켜 둔 터미널은 실행 중이라 입력이 어렵습니다.
VS Code에서 Terminal → New Terminal로 터미널을 하나 더 열고 , 그 새 터미널에서 아래를 실행하세요.
Tip
03편처럼 -Body '{ "title": "..." }'로 보내도 되지만, 04편부터는 한글 깨짐 방지 + 가독성 때문에 $body = @{ ... } | ConvertTo-Json 방식을 씁니다. (영어만 쓸 거면 03편 방식도 동작합니다.)
Copy$body = @ { title = "파일로 저장되는 TODO" } | ConvertTo-Json - Compress
Invoke-RestMethod - Method Post - Uri "http://localhost:3000/todos" - ContentType "application/json; charset=utf-8" - Body $body
Expected result
정상이라면 ok: true와 함께 TODO가 생성되고, id가 들어갑니다.
6) todos.json 파일 확인#
VS Code에서 todos.json을 열어보면, 방금 추가한 TODO가 파일에 들어가 있습니다.
Expected result
정상이라면 todos.json 안에 TODO 객체가 저장되어 있습니다. (id/title/done/createdAt)
Tip
파일이 너무 길면, VS Code에서 Alt + Shift + F(포맷)로 보기 좋게 정리할 수 있습니다.
(이미 코드는 2칸 들여쓰기로 저장하므로 보통은 필요 없습니다.)
7) “진짜로” 저장됐는지 확인 (서버 껐다 켜기)#
이제 제일 중요한 확인입니다.
서버가 실행 중인 터미널에서 Ctrl + C로 서버 끄기
다시 실행: node server.js
브라우저에서 GET /todos 확인
브라우저 주소:
http://localhost:3000/todos
Expected result
정상적으로 서버를 재시작했는데도 items 안에 TODO가 남아있습니다.
8) 작동 원리#
Note
03편과의 차이를 한 줄로 정리하면 이겁니다.
03편: TODO가 “메모리(램)”에만 있어서 서버를 끄면 사라짐
04편: TODO를 “디스크 파일(todos.json)”에 저장해서 서버를 껐다 켜도 남음
Copyserver.js 시작 ──> todos.json 읽기(load) ──> 메모리(todos 배열)
POST /todos ──> 메모리(todos 배열) 추가 ──> todos.json 저장(save)
다음 글#
다음 글에서는 실전 감각을 위해 검증/에러처리/간단 인증 을 묶어서 정리합니다.
다음 글: 05. 실전 감각 3종 세트
(
"fs"
);
const path = require ( "path" );
const app = express ();
const PORT = 3000 ;
app. use (express. json ());
// todos.json 파일 경로 (server.js와 같은 폴더)
const DATA_PATH = path. join (__dirname, "todos.json" );
function loadTodosFromFile () {
// 파일이 없으면 빈 배열로 시작
if ( ! fs. existsSync ( DATA_PATH )) return [];
// 1) 파일을 텍스트로 읽기
const raw = fs. readFileSync ( DATA_PATH , "utf-8" ). trim ();
// 파일이 비어있으면 빈 배열로 처리
if (raw. length === 0 ) return [];
// 2) 텍스트(JSON)를 JS 값(배열)로 변환
const parsed = JSON . parse (raw);
// 최소한의 형태 체크
if ( ! Array. isArray (parsed)) {
throw new Error ( "todos.json must be an array like []" );
}
return parsed;
}
function saveTodosToFile ( todos ) {
// JS 값(배열)을 JSON 텍스트로 바꿔서 파일에 쓰기
fs. writeFileSync ( DATA_PATH , JSON . stringify (todos, null , 2 ), "utf-8" );
}
// 1) 시작할 때 파일에서 읽어서 메모리로 올림
let todos = loadTodosFromFile ();
// 2) 저장된 TODO 중 가장 큰 id를 찾아서, 그 다음 번호부터 시작 (재시작해도 id가 이어짐)
let nextId = 1 ;
for ( const t of todos) {
if ( typeof t.id === "number" && t.id >= nextId) {
nextId = t.id + 1 ;
}
}
app. get ( "/health" , ( req , res ) => {
res. json ({ ok: true });
});
app. get ( "/todos" , ( req , res ) => {
res. json ({ items: todos });
});
app. post ( "/todos" , ( req , res ) => {
const title = req.body?.title;
if ( typeof title !== "string" || title. trim (). length === 0 ) {
return res. status ( 400 ). json ({
ok: false ,
error: "title is required" ,
example: { title: "물 마시기" },
});
}
const todo = {
id: nextId ++ ,
title: title. trim (),
done: false ,
createdAt: new Date (). toISOString (),
};
todos. push (todo);
// 3) 추가될 때마다 파일에 저장
saveTodosToFile (todos);
res. status ( 201 ). json ({ ok: true , item: todo });
});
app. listen ( PORT , () => {
console. log ( `Server running: http://localhost:${ PORT }` );
});