Welcome back! In the last lesson, you learned how to use the Repository Pattern to keep your data logic separate from your business logic. Up until now, you have been storing your data in memory, which means all your tasks disappear every time you restart your app. This is fine for learning, but in real-world applications, you need your data to persist even after the server restarts.
In this lesson, you will learn how to save your data to a file using JSON. This is called file-based persistence. By the end of this lesson, you will know how to read and write your tasks to a JSON file, making your app more like a real backend service.
Let’s quickly remind ourselves of the Task
data structure and where our data lives. You have a Task
type that looks like this:
Previously, you stored tasks in memory using an array. Now, you will use a file called data/tasks.json
to store your tasks. This file will look like this when empty:
This file will grow as you add, update, or delete tasks. The rest of your project structure stays the same, but now your data will be saved in a file instead of just in memory.
To work with files in Node.js, you use the built-in fs/promises
module. This module lets you read and write files using promises, which means you can use async
and await
for cleaner code.
Here’s a simple example of reading and writing a JSON file:
Explanation:
fs.readFile
reads the file as a string.JSON.parse
turns the string into a JavaScript array.fs.writeFile
saves the array back to the file as a JSON string.JSON.stringify(tasks, null, 2)
makes the JSON file easy to read by adding indentation. The second and third arguments inJSON.stringify(tasks, null, 2)
ensure the JSON file is pretty-printed with 2-space indentation, making it easier to inspect manually.
Output Example:
If you call writeTasks([{ id: 1, title: "Learn Next.js", completed: false }])
, your tasks.json
file will look like:
Now, let’s build a repository that uses the JSON file for storage. Here’s the main code for your file-based repository:
Explanation:
read()
loads all tasks from the JSON file. It also updatesnextId
so new tasks get unique IDs.write(tasks)
saves the current list of tasks to the file.- Each method (
getAll
,getById
,create
, , , ) reads the file, makes changes, and writes back if needed.
One of the main benefits of using the Repository Pattern is how easily you can swap between data sources. In the previous lesson, your taskService.ts
used an in-memory repository:
To persist tasks to disk, you simply switch to:
The rest of your service logic — and your entire API — doesn’t change at all. That’s because both mapTaskRepository
and fileTaskRepository
conform to the same TaskRepository
interface. Since the interface defines the expected contract, the service functions can remain the same regardless of the underlying implementation.
Now, let’s connect your new file-based repository to the service layer. This is where your business logic lives. Here’s how you update your service to use the new repository:
Explanation:
- The service now uses
fileTaskRepository
instead of the in-memory one. - All your service functions (
getAllTasks
,createTask
, etc.) now work with data stored in the JSON file. - This change is invisible to the rest of your app, thanks to the Repository Pattern.
In this lesson, you learned how to move from in-memory storage to file-based persistence using JSON files. You saw how to:
- Read and write JSON files in Node.js using
fs/promises
- Build a repository that saves tasks to a file
- Connect your service layer to use the new file-based repository
This makes your app more production-ready, as your data will now survive server restarts. In the next practice exercises, you’ll get hands-on experience working with file-based persistence. Great job making it this far — let’s keep going!
