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.
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' })
}
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,
- read data in userData.json and
- 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.
Top comments (2)
If you deploy it to Netlify or some other server, you will get
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.
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");