In this post, I will show you how to create Next.js API routes and how to make requests to those APIs from Frontend.
Table of Contents
Next.js API Routes
- Next.js is a production-ready React Framework You can write frontend code for user interaction and backend Rest API that handles requests and responses.
- Routing in Nex.js is based on the file system, which means when you add a file to the pages folder in your Next.js project, that file is automatically mapped to a new route. API routing follows this same pattern.
- Anything inside the
pages/api
folder is an API endpoint. You can add all your API handler functions and the associated logic inside this folder( theapi
folder ). - The code you write in
api
folder is bundled with the server-side code, and therefore it will not affect the bundled size of the frontend ( client-side ) code
Creating an API Route
run npx create-next-app
demo-next-app on the terminal to create a Next.js app.
In the default settings, there is hello.js in the pages/api
folder. This file matches the http://localhost:3000/api/hello
pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
Now run the default app, make an API request with http://localhost:3000/api/hello
in the browser
You should get json response of {"name":"John Doe"}
with status code 200. ( Note: you can check the status code in the Network tab in the developer tools )
When this API request handler receives an API request, req
, the parameter holds the IncomingMessage and pre-built middlewares that you can make use of.
The second argument res carries the ServerResponse object and helper function that you can utilize in sending the API response.
Making API requests
Now let us see how to make GET, POST, DELETE, and PATCH requests to API.
We are going to build a simple CRUD application for comments management.
Setting up the dev environment
We are going to use a JSON file to record data ( comments ) for this application. we will UUID package to generate UUIDs to keep a unique ID for each record in the JSON file.
Now, delete the default file create( hello.js) from the pages/api
folder, and, create another folder named comments in the api
folder.
Inside the comments
folder, create a file name index.js, in which we will be writing the API request handler function.
Making GET request
- First, we are going to add some test data and add the GET request
- Add the following data to comets.js in the
data
folder
[{ "id": 1 , "text": "this is the first comment"} , { "id": 2 , "text": "this is the second comment"}, { "id": 3 , "text": "this is the third comment"}]
- Now, add the following code index.js file at
api/commnets
api/commnets/index.js
import path from 'path';
import fs from 'fs'; //or as const fs= require('fs');
export default async function handler( req, res ){
let comments = [];
//reading the records from the JSON file
try{
comments= JSON.parse( fs.readFileSync( path.join( process.cwd(), 'data/comments.json')) );
}catch(err){
console.log(err)
}
if( req.method === 'GET'){
res.status( 200 ).json( comments );//sending the response
}
}
As you can see in the above code, we need to check the incoming request type. We use the req.method
property for this.
You might wonder why I did not place the code that read the file inside the if statement. Of course, you can place it. However, as we are going to add more code to other types of HTTP requests, and reading the file is required to show the updated data, I placed this code outside of the if statement.
- After we add the API handler, it is time to add the front-end code so that we can see the data we read.
- Create a folder named comments in the page folder, and add an index.js file. Add the following code to it.
import { useEffect, useState } from "react";
export default function Comments(){
const [ comments, setComments ] = useState([]);
useEffect(()=>{
getComments();
},[]);
//Making GET request to the API
const getComments = async () =>
{
const response = await fetch('/api/comments');
const comments = await response.json();
setComments ( comments );
};
return(
<div>
{ comments.map( comment => <li keys={ comment.id } >{ comment.text } </li>) }
</div>
)
}
As Next.js uses React components to display the UI and its logic, if you know React, you should be able to understand the above code quite easily.
Making POST request
We are going to add the code to handle post request
First, go to the pages/api/index.js and add the following import statement
import uuid from 'uuid';//or as const uuid = require('uuid');
Add the following code to pages/api/index.js below the code for the GET request
if( req.method === 'POST'){
const uniqueRandomID = uuid.v4();//creating unique id
const newcomment = {
id: uniqueRandomID,
text: req.body.comment
}
comments.push(newcomment);
//writing to the incoming data to the JSON file
fs.writeFileSync( path.join( process.cwd(), 'data/comments.json'), JSON.stringify(comments));
res.status( 201 ).json( comments);
}
Add the code for UI in the return statement of the pages/comments/index.js file
return(
<div>
{ comments.map( comment => <li keys={ comment.id } >{ comment.text } </li>) }
<input ref={inputEl} type='text' onChange={ (e) => setState(e.target.value) }/>
<button onClick={ addComments }> add comment</button>
</div>
)
As you can see I have added the input tag and a button with the relevant function. We need to add the setState()
and addComments()
functions.
Now in the same file add the following code
const [ state, setState ] = useState([]);
const inputEl = useRef(null);
//post request to the API
const addComments = async () => {
await fetch('/api/comments',
{
method:'POST',
body: JSON.stringify( { comment: state }),
headers: {
'Content-Type': 'application/json'
}
}
);
getComments();//called Get request to refresh the display
inputEl.current.value = '';
}
I use the state to hold the input entered into the text box. So, this state container the new comment entered.
Then, I used that state in the post request. The new comment will be added to the comments.json file. After that, because I need to display all the comments again on the browser I called the getComments()
method, which makes the GET request.
Making PATCH request
For the patch request, we need to update the UI again. We are going to add an Edit button in the return statement of the React component of pages/comments/index.js
return(
<div>
{ comments.map( comment =>
<li keys={ comment.id } >{ comment.text }
<button onClick={ () => editComment( comment.id , comment.text) }>edit</button>
</li>)
}
<input ref={inputEl} type='text' onChange={ (e) => setState(e.target.value) }/>
<button onClick={ addComments }> add comment</button>
</div>
)
When you click on the edit button, the editComments()
function that takes the comment ID and the comment text executes.
we have not yet implemented the setUIForUpdates function, which prepares the UI for updating a selected comment
const setUIForUpdates = async ( id , text ) => {
setCurentId( id ); //set the id of the selected commnet
setState( text );//set the state with the comment text
inputEl.current.value = text;//change the value of the input element
}
The above code has a new function named setCurentId
. This function sets the new State currentId
work as a container to hold the selected comment. Add this new state along with other states we used before.
const [ comments, setComments ] = useState([]);
const [ state, setState ] = useState([]);
const inputEl = useRef(null);
const [ currentId , setCurentId ] = useState(null);
Now add the code that makes the PATCH request
const UpdateComment = async () =>{
const response = await fetch('/api/comments',
{
method:'PATCH',
body: JSON.stringify( { id: currentId, comment: state }),
headers: {
'Content-Type': 'application/json'
}
}
);
setState(response.json())
getComments();
inputEl.current.value = '';
}
First, add update handler code for the patch request in the api/comments.js.
if( req.method === 'PATCH'){
const { id , comment } = req.body;
try{
const index = comments.findIndex( comment => comment.id === id );
comments[ index ].text = comment;
fs.writeFileSync( path.join( process.cwd(), 'data/comments.json'), JSON.stringify(comments));
}catch( err ){
console.log( err );
}
res.status( 200 ).json( comments );
}
As you can see in the above code, we receive an id and comment from the req.body
. We have not added the code responsible for this in the frontend code.
Let’s add the code that makes the PATCH request.
const UpdateComment = async () =>{
const response = await fetch('/api/comments',
{
method:'PATCH',
body: JSON.stringify( { id: currentId, comment: state }),
headers: {
'Content-Type': 'application/json'
}
}
);
setState(response.json())
getComments();// to get the updated record from comments.js
clear(); //set currentID to null & clear the input text box
}
Making DELETE request
We are going to handle the DELETE request differently. We are going to send the Id of the common as a query string.
We can do this by creating a dynamic API route in Next.js. You can read more on Next.js dynamic routes in this post.
- In the
pages/api/comment
, create a file named [commentId].js - Add the following handler function
import path from 'path';
import fs from 'fs' //or' const fs= require('fs');
export default function handler( req, res ){
const { commentId } = req.query;
const comments = JSON.parse( fs.readFileSync( path.join( process.cwd(), 'data/comments.json')) );
if( req.method === 'DELETE'){
const index = comments.findIndex( comment => ( comment.id === commentId) );
comments.splice( index , 1);//deleting the record
fs.writeFileSync( path.join( process.cwd(), 'data/comments.json'), JSON.stringify(comments));
res.status( 200 ).json( "Record deleted");
}
}
comments.findIndex() method is to find the index of the comments array that satisfies the ( comment.id === commentId )
condition. The commentId
is the ID of the comment that you choose to delete. This ID passes from the Frontend to the Backend via the query
object.
Summary
We discussed what API routes are in Next.js, how to create API routes, and how to make API requests such as GET, POST, PATCH, and DELETE important in CRUD applications.