Read/Write On Local JSON File With Next.js - part 5.1
Tags: nextjs, tutorial, javascript, webdev
Published: 2023-03-27 13:35:15

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](https://en.wikipedia.org/wiki/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](https://nextjs.org/docs/api-routes/introduction) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i9ijwlnnwrgjtsubsou1.png)

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](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) and some data in a JSON format.

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

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);
  }

  ...
}
```

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>
```

You should see the following log in the console:

```
{name: 'John Doe'}
```

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: 

```
[]
```
### 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);
  }

}
``` 

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);
  }
```

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
  }
```

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' });
    }
```

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 (...)
}

```
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.