DEV Community

Cover image for Read/Write On Local JSON File With Next.js - part 5.1
Lorenzo Zarantonello for This is Learning

Posted on

Read/Write On Local JSON File With Next.js - part 5.1

With the previous post, I got pretty excited about reading data from a local JSON file. So, I thought I could write into a JSON file in a similar fashion.

However, since getStaticProps is executed during the build process, any changes made to the data.json file will only be reflected when you rebuild your app.

This is a bit of a bummer but there are other, possibly better, ways.

In this post, we'll see how to read and write into a local JSON file in Next.js.

This requires us to set up an API endpoint and if you are not familiar with a backend, it could get a bit confusing. Feel free to ask questions in the comments if that is the case.

Setting Up An API

Next.js offers something called API Routes to set up an API in our app.

According to the docs, Any file inside the folder pages/api is mapped to /api/* and will be treated as an API endpoint instead of a page. They are server-side only bundles and won't increase your client-side bundle size.

So we create a file in pages/api and we call it storeJSONData.js. In this file, we will create a little backend.

storeJSONData

This file will be treated as an endpoint, meaning that it will handle HTTP requests. In our case, we will see those requests at http://localhost:3000/api/storeJSONData.

Using The API

To make sure everything works fine, we test the newly created API by adding the following code in storeJSONData.js.

The handler function handles GET requests to the url: /api/storeJSONData and returns status 200 and some data in a JSON format.



export default function handler(req, res) {
  res.status(200).json({ name: 'John Doe' })
}


Enter fullscreen mode Exit fullscreen mode

Then, we can create the following function inside the Home component. The fetchData function is an asynchronous function that sends a GET request to the url: /api/storeJSONData.

Then, it receives a response that is a Promise. When the Promise completes we parse it as JSON and finally we log the data we receive.




...
export default function Home({ localData }) {

  const fetchData = async () => {
    const response = await fetch('/api/storeJSONData')
    const data = await response.json();
    console.log(data);
  }

  ...
}


Enter fullscreen mode Exit fullscreen mode

The function can be triggered in several ways. For a quick test, use a button in the template of the Home component and click it.



<button onClick={fetchData}>Fetch</button>


Enter fullscreen mode Exit fullscreen mode

You should see the following log in the console:



{name: 'John Doe'}


Enter fullscreen mode Exit fullscreen mode

As you can see from the code above, fetchData is a function in the component and it is not inside a getStaticProps. This is something to remember: You should not fetch an API Route from getStaticProps or getStaticPaths because they run on the server-side and not on the client-side.

GET And POST Requests

The previous example makes sure everything works.

Now we write a GET request that reads data from a JSON file.

First, create a new JSON file inside the json folder. We will call it userData.json. The initial content can be an empty array:



[]


Enter fullscreen mode Exit fullscreen mode

HTTP GET

In storeJSONData.js we write the logic to read storeJSONData.json.

If the request method of the HTTP request is GET,

  1. read data in userData.json and
  2. when the Promise completes send a response with status 200 and data.


import fsPromises from 'fs/promises';
import path from 'path';

const dataFilePath = path.join(process.cwd(), 'json/userData.json');

export default async function handler(req, res) {

  if (req.method === 'GET') {
    // Read the existing data from the JSON file
    const jsonData = await fsPromises.readFile(dataFilePath);
    const objectData = JSON.parse(jsonData);

    res.status(200).json(objectData);
  }

}


Enter fullscreen mode Exit fullscreen mode

Now, we can change the fetchData function we created above to send an HTTP GET request to api/storeJSONData and log the response.



const fetchData = async () => {
    const response = await fetch('/api/storeJSONData')
    const data = await response.json();
    console.log("data", data);
  }


Enter fullscreen mode Exit fullscreen mode

At this point, you should see an empty array logged in your console.

We can create the logic to write data in storeJSONData.json through HTTP POST requests.

HTTP POST

Let's expand the code in storeJSONData.js to take into consideration POST requests as follow:



if (req.method === 'GET') {
    // Read the existing data from the JSON file
    const jsonData = await fsPromises.readFile(dataFilePath);
    const objectData = JSON.parse(jsonData);

    res.status(200).json(objectData);
  } else if (req.method === 'POST') {
    // Code for POST requests goes here
  }


Enter fullscreen mode Exit fullscreen mode

Inside the condition, we can write the following logic and add some error handling:



try {
      // Read the existing data from the JSON file
      const jsonData = await fsPromises.readFile(dataFilePath);
      const objectData = JSON.parse(jsonData);

      // Get the data from the request body
      const { name, email } = req.body;

      // Add the new data to the object
      const newData = {
        name,
        email
      };
      objectData.push(newData);

      // Convert the object back to a JSON string
      const updatedData = JSON.stringify(objectData);

      // Write the updated data to the JSON file
      await fsPromises.writeFile(dataFilePath, updatedData);

      // Send a success response
      res.status(200).json({ message: 'Data stored successfully' });
    } catch (error) {
      console.error(error);
      // Send an error response
      res.status(500).json({ message: 'Error storing data' });
    }


Enter fullscreen mode Exit fullscreen mode

You might notice some repetitions and it could be good to refactor the code to follow DRY principles.

However, that is up to you.

Finally, you can create a function inside Home component, just after fetchData.



...
export default function Home({ localData }) {  
  ...
  const fetchData = async () => { ... }  

  const saveData = async () => {
    const response = await fetch('/api/storeJSONData', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ name: "Lorenzo", email: "lo@lorenzozar.com" })
      });
      const data = await response.json();
      console.log(data);
  }

  return (...)
}



Enter fullscreen mode Exit fullscreen mode

In this example, you can trigger the function using a button as we did before. Notice that we are passing some hard-coded data { name: "Lorenzo", email: "email@email.com" } but that can be swapped for user inputs by using a form and react hooks.

Top comments (2)

Collapse
 
ashisbiswas profile image
Ashis Biswas • Edited

If you deploy it to Netlify or some other server, you will get

"Error: EROFS: read-only file system"

You can only upload to tmp directory but the directory is non-persistent. By using the above code you can only read the json file that is present on build time. So the above code will not going to work.

Collapse
 
ttsoares profile image
Thomas TS

That line
const dataFilePath = path.join(process.cwd(), 'json/userData.json');
did not work here...
To work:
const dataFilePath = path.join(process.cwd(), "/pages/api/json/userData.json");