Besides Next.js being a great front-end framework for React, it also provides a simple way to build an API for your front-end - all in the same web app!
As of version 9, Next.js provides API routes that allows developers to create API endpoints using the Next.js folder structure. We can use API endpoints to build either a RESTful API, or a GraphQL API. This article will focus on a RESTful API for the sake of simplicity.
With Next.js API routes, there's no longer a need to set up a back-end Node.js server to build an API. Building out two separate projects for the front-end and back-end of an application introduces its own set of challenges. Next.js simplifies this process by becoming a full-stack web framework with the addition of API routes. Building and deploying a full-stack web app has never been faster and easier!
Setting up API routes
API routes go in the pages/api
directory of a Next.js project.
When a file or folder is added to this pages/api
folder, Next.js will create an API endpoint URL for it. Creating a pages/api/users.ts
file will create an /api/users
API endpoint. We can also create an /api/users
API endpoint by creating a pages/api/users/index.ts
file.
To create a dynamic API route for a specific user, we can create a pages/api/users/[id].ts
file. This dynamic route will match requests such as /api/users/1
.
Just like the pages
folder maps its files and folders to URLs that can be visited in a web browser, the pages/api
folder maps its files and folders to API endpoint URLs.
Creating API routes
To begin, let's create a new Next.js project called nextjs-full-stack
.
npx create-next-app full-stack-nextjs
cd full-stack-nextjs
If you prefer using TypeScript with Next.js, you can use npx create-next-app --ts
instead. For this article, we'll just use JavaScript.
Notice that newly created Next.js project contains a pages/api/hello.js
file. An /api/hello
API endpoint has already been provided for us. Let's run the project using npm run dev
to try this API endpoint.
Visit http://localhost:3000/api/hello
in a web browser. You should see the following response.
{ "name": "John Doe" }
Understanding API routes
Let's take a look at the code in the pages/api/hello.js
file.
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
The default export in files that are found within the api
folder must be a function. Each function is a handler for a particular route. Asynchronous functions are also supported for cases where requests need to be made to external APIs or a database.
When running the Next.js project locally, it creates a Node.js server and passes the request
and response
objects from the server into the handler function of the requested endpoint.
A dynamic route
Let's create a pages/api/users/[id].js
file to create an API route for a specific user. Let's add the following code within this file.
const users = [
{ id: 1, name: 'John Smith' },
{ id: 2, name: 'Jane Doe' },
];
export default (req, res) => {
const { query: { id } } = req;
res.json({
...users.find(user => user.id === parseInt(id)),
});
}
For the purposes of creating an example, we mocked up a list of users within the file. More realistic usage might involve querying a database for the requested user.
The id
value is retrieved from the request query parameter, and then used to find the corresponding user object in the list of users. Keep in mind that the id
from the request is a string, so we must parse it to an integer in order to perform a user lookup by id on the users list.
Visit http://localhost:3000/api/users/1
in a web browser. You should see the following response.
{ "id": 1, "name": "John Smith" }
Returning an error
We can modify the user endpoint we just created to return an error when the requested user id is not found in a list of users.
const users = [
{ id: 1, name: 'John Smith' },
{ id: 2, name: 'Jane Doe' },
];
export default (req, res) => {
const { query: { id } } = req;
const user = users.find(user => user.id === parseInt(id));
if (!user) {
return res.status(404).json({
status: 404,
message: 'Not Found'
});
}
res.json({ ...user });
}
Visit http://localhost:3000/api/users/3
in a web browser. You should see the following response since no user with an id
of 3
exists.
{ "status": 404, "message": "Not Found" }
Handling multiple HTTP verbs
Next.js API routes allow us to support multiple HTTP verbs for the same endpoint all in one file. The HTTP verbs that we want to support for a single API endpoint are specified in the request handler function.
Let's create a pages/api/users/index.js
file to create an API route for all users. We want this endpoint to support GET
requests to get all users and POST
requests to create users. Let's add the following code within this file.
export default (req, res) => {
const { method } = req;
switch (method) {
case 'GET':
res.json({ method: 'GET', endpoint: 'Users' });
break;
case 'POST':
res.json({ method: 'POST', endpoint: 'Users' });
break;
default:
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${method} Not Allowed`);
break;
}
}
The req.method
value will tell us what HTTP verb was used to make the request. We can use a switch
statement to support multiple HTTP verbs for the same endpoint. Any HTTP requests that are not GET
or POST
, requests will enter the default
case and return a 405 Method Not Allowed
error.
Visit http://localhost:3000/api/users
in a web browser. You should see the following response.
{ "method": "GET", "endpoint": "Users" }
We can use an API platform like Postman or Insomnia to test POST
, PUT
, and DELETE
requests to this API endpoint.
Testing the POST
request will return the following response.
{
"method": "POST",
"endpoint": "Users"
}
Testing PUT
and DELETE
requests will return Method Not Allowed
errors with a 405 Method Not Allowed
response code.
Using API routes from the front-end
Let's create a users/[id].js
page to retrieve a specific user when the page loads, and then mimic a save profile operation when the form on the page is submitted.
We will use client-side rendering within Next.js for this page. Client-side rendering is when the client makes requests for API data. The approach used in this example can also apply for pages that use server-side Rendering (SSR).
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
export default function User() {
const router = useRouter();
const { id } = router.query;
const [name, setName] = useState();
// GET request to get a user
useEffect(() => {
// wait for the useRouter hook to asynchronously get the query id
if (!id) {
return;
}
const fetchUser = async () => {
const response = await fetch(`/api/users/${id}`, {
method: "GET",
headers: {
"Content-Type": "application/json"
},
});
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
const user = await response.json();
setName(user?.name);
}
fetchUser();
}, [id]);
// POST request to mimic the saving of a user
const onSubmit = async (e) => {
e.preventDefault();
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({}),
});
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
const data = await response.json();
console.log('POST: ', data);
};
return (
<div>
<h1>User Form</h1>
<form onSubmit={onSubmit}>
<div>
<label htmlFor="name">Name</label>
<input
type="text"
id="name"
name="name"
value={name ?? ''}
onChange={(e) => setName(e.target.value)}
/>
</div>
<button type="submit">Submit</button>
</form>
</div>
);
}
To retrieve a user by id
when the page loads, we can use the React useEffect
hook with the id
as a dependency. The browser-supported Fetch API is used to make a GET
request to /api/users/[id]
. Async/await is used to wait for this asynchronous request to complete. Once it is completed, the user's name is saved to the component state using setName
, and displayed within the input text box.
When the form is submitted, a POST
request is made to the /api/users
API route that we created earlier. To keep things simple, the response of the request is then logged to the console.
Conlusion
Next.js makes it fast and easy to build an API for your next project. A very good use case to get started with Next.js API routes is for the implementation of CRUD operations to handle form input.
API endpoints are created as Node.js serverless functions. You can build out your entire application's API with Next.js API routes. Go ahead and build something awesome with them!