Route handler allows us to define API endpoints with custom logic used to handle different HTTP request. Similar to how you can define webpages using page.jsx
, these API endpoints are created using route.js
.
What is an API endpoint
API stands for application programming interface, and it serves as a bridge, allowing the client to communicate with the server. The client sends an HTTP request to the API endpoint, and the endpoint processes the request and responds accordingly. Instead of rendering a webpage, the API endpoints returns data, structured data, usually in JSON format.
Next.js supports 7 different types of HTTP requests, GET
, POST
, PUT
, PATCH
, DELETE
, HEAD
, and OPTIONS
. Each of them serves a specific purpose:
GET
: Retrieves data from server.POST
: Adds new data to the server.PUT
: Updates existing data, such as updating a blog post with new content.PATCH
: Updates part of existing data, such as updating the title of the blog post.DELETE
: Removes data from server.HEAD
: Retrieves the metadata of a resource, without transmitting the resource itself.OPTIONS
: Returns the allowed HTTP methods for a specific API endpoint.
Creating an API route handler
To define an API endpoint using Next.js, create a route.js
file like this:
1app
2├── api
3│ └── users
4│ └── route.js <===== Endpoint: /api/users
5├── blog
6├── dashboard
7├── favicon.ico
8├── fonts
9├── globals.css
10├── layout.jsx
11└── page.jsx
It is customary to design your API endpoints so that they all start with /api
. Of course, you can choose to not follow this custom, but note that you must design your routes carefully so that your API endpoints do not conflict with the regular page routes.
It is not allowed to have both page.jsx
and route.js
under the same directory.
1app
2├── blog
3├── dashboard
4│ ├── layout.jsx
5│ ├── page.jsx
6│ └── users
7│ ├── page.jsx <===== This is not allowed
8│ └── route.js <=====
9├── favicon.ico
10├── fonts
11├── globals.css
12├── layout.jsx
13└── page.jsx
Inside the route.js
file, you can define custom logic to handle different requests. For instance, the GET()
function handles a GET
request:
app/api/users/route.js
1import { NextResponse } from "next/server";
2
3export function GET(request) {
4 const searchParams = request.nextUrl.searchParams;
5 const query = searchParams.get("query");
6
7 console.log("Search Query:", query);
8
9 return NextResponse.json({ query: query, message: "Search query received." });
10}
Next.js extends the native Request and Response APIs with NextRequest and NextResponse, which provide a few helper functions.
In the above example, request.nextUrl.searchParams
retrieves the search parameters from the request. In case you forgot what are search params, they are parameters embedded into the URL, which look like this:
1http://localhost:3001/api/users?query=123
In this case, the URL contains a search parameter query
, whose value is 123
.
1const searchParams = request.nextUrl.searchParams;
2const query = searchParams.get("query");
request.nextUrl.searchParams
retrieves all search parameters, and searchParams.get("query")
gets the one named query
.
Now, let's test this API endpoint by sending a GET
request. Open Thunder Client and send a request like this:
And you should get the following response:
1{
2 "query": "123",
3 "message": "Search query received."
4}
Try sending another request with a different HTTP method.
An error will be returned since we did not define it. The GET()
function only deals with GET
requests.
Route handler and dynamic routing
Similar to a regular page route, API route handlers also allows dynamic routing.
1app
2├── api
3│ └── users
4│ └── [id] <===== Defines a parameter id
5│ └── route.js <===== Endpoint: /api/users/[id]
6├── blog
7├── dashboard
8├── favicon.ico
9├── fonts
10├── globals.css
11├── layout.jsx
12└── page.jsx
Part of the URL will be captured and assigned to parameter id
, which can then be accessed inside route.js
like this:
1import { NextResponse } from "next/server";
2
3export async function GET(request, { params }) {
4 const id = (await params).id;
5
6 const searchParams = request.nextUrl.searchParams;
7 const query = searchParams.get("query");
8
9 console.log("ID:", id);
10 console.log("Search Query:", query);
11
12 return NextResponse.json({
13 query: query,
14 id: id,
15 message: "Search query received.",
16 });
17}
And send a GET request to the endpoint http://localhost:3001/api/users?query=123
:
You should get the following response in return:
1{
2 "query": "123",
3 "id": "10",
4 "message": "Search query received."
5}
Fetching from the API endpoint
Inside the pages and components, you can call the API using the standard fetch()
function.
1export default async function Page() {
2 let response = await fetch("http://localhost:3001/api/users/10?query=123");
3 let data = await response.json();
4
5 console.log(data);
6
7 return <div>. . .</div>;
8}
This works for both server and client components.
Form handling with API endpoint
Finally, let's take a look at how these API routes and the pages can work together by creating a form handling example.
1src/app
2├── api
3│ └── users
4│ └── route.js <===== API endpoint /api/users
5├── favicon.ico
6├── globals.css
7├── layout.jsx
8├── page.jsx
9└── users
10 └── page.jsx <===== Webpage /users
We need two files to make this work, an API endpoint app/api/users/route.js
, and a webpage app/users/page.jsx
.
Inside the webpage, we should have a form, and when the form is submitted, it should send a POST request to the endpoint /api/users
.
The endpoint processes the request, do something in the backend with the transferred data, and returns a response.
The response gets sent back to the webpage, the webpage checks if the response is an error, and displays either an error message or success message.
app/users/page.jsx
1"use client";
2
3import { useState } from "react";
4
5export default function NewUser() {
6 const [name, setName] = useState("");
7 const [email, setEmail] = useState("");
8 const [message, setMessage] = useState("");
9
10 const handleSubmit = async (e) => {
11 e.preventDefault();
12
13 const response = await fetch("/api/users", {
14 method: "POST",
15 headers: { "Content-Type": "application/json" },
16 body: JSON.stringify({ name, email }),
17 });
18
19 if (response.ok) {
20 setMessage("User created successfully!");
21 setName("");
22 setEmail("");
23 } else {
24 const errorData = await response.json();
25 setMessage(`Error: ${errorData.message}`);
26 }
27 };
28
29 return (
30 <div>
31 <h1>Create New User</h1>
32 <form onSubmit={handleSubmit}>
33 <div>
34 <label htmlFor="name">Name:</label>
35 <input
36 type="text"
37 id="name"
38 value={name}
39 onChange={(e) => setName(e.target.value)}
40 required
41 />
42 </div>
43 <div>
44 <label htmlFor="email">Email:</label>
45 <input
46 type="email"
47 id="email"
48 value={email}
49 onChange={(e) => setEmail(e.target.value)}
50 required
51 />
52 </div>
53 <button type="submit">Submit</button>
54 </form>
55 {message && <p>{message}</p>}
56 </div>
57 );
58}
app/api/users/route.js
1import { NextResponse } from "next/server";
2
3export async function POST(request) {
4 const { name, email } = await request.json();
5
6 // Do something with the name and email
7 console.log(name);
8 console.log(email);
9
10 return NextResponse.json({ name, email });
11}
In this example, since we haven't discussed how to integrate with a database, we are only simulating the data processing with console.log()
.
You can check if the form works by going to http://localhost:3001/users
:
Fill in the information and submit the form, you should see the following logs in the backend.