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.readFilereads the file as a string.JSON.parseturns the string into a JavaScript array.fs.writeFilesaves 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 updatesnextIdso 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
fileTaskRepositoryinstead 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!
