File Persistence with JSON
File Persistence with JSON: Making Tasks Survive Server Restarts
Welcome back! You’ve built a clean backend foundation:
- Routes call services
- Services call repositories
- Your validation + response formatting is consistent
The last missing piece (before we shift hard into frontend work) is persistence—because right now, restarting the server wipes everything.
In this lesson, you’ll replace “memory-only” storage with a simple but realistic persistence layer: a JSON file on disk. You’ll implement a fileTaskRepository that reads/writes data/tasks.json, wire the service layer to use it, and seed the JSON file with nicer sample tasks so your API (and soon your UI) has meaningful data to work with.
Previously…
In the previous lesson, you introduced the Repository Pattern and implemented mapTaskRepository using a Map. That gave you:
- clean separation
- swap-ability
Now we’ll take advantage of that design:
- introduce a new repository implementation (
fileTaskRepository) - switch the service layer to use it
- without changing route code or API contracts
How we’ll use Codex CLI in this lesson
This is the final backend-focused lesson, so the goal is to make a small number of high-impact changes with tight prompts:
- Implement the file repository in one file
- Switch
taskService.tsto use it - Seed
data/tasks.jsonwith better sample tasks
A good prompt format:
Codex, modify only the files I list:
src/lib/repositories/fileTaskRepository.ts
src/lib/services/taskService.ts
data/tasks.jsonImplement file persistence using JSON exactly as described, and keep all exports and signatures unchanged.
Show full updated contents for each modified file.
Persisting tasks with a file-backed repository
The core of persistence is simple:
- read the JSON file into memory
- make a change
- write it back
In this project, the file-backed repository is implemented in src/lib/repositories/fileTaskRepository.ts.
Reading and writing the JSON file
This repository uses Node’s fs/promises for async file operations and path to create a stable file path to data/tasks.json.
FILE_PATH uses process.cwd() to anchor the path at your project root, then points to data/tasks.json. This is a simple, container-friendly way to ensure the file location is predictable.
read() is defensive: if the file is missing, invalid, or unreadable, it returns [] instead of crashing your API
That keeps your backend stable even if the data file gets wiped or corrupted during development.
nextId is recalculated after reading tasks by finding the maximum existing id. This prevents ID collisions across restarts, which is the main thing that “breaks” when you move from memory to persistence.
