API Route Handler

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:

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

text
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

javascript
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:

text
1http://localhost:3001/api/users?query=123

In this case, the URL contains a search parameter query, whose value is 123.

javascript
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:

Next API route with search params

And you should get the following response:

text
1{
2  "query": "123",
3  "message": "Search query received."
4}

Try sending another request with a different HTTP method.

Method not allowed

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.

text
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:

javascript
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:

Next API route with dynamic routes

You should get the following response in return:

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

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

text
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

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

javascript
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:

User form

Fill in the information and submit the form, you should see the following logs in the backend.

Logged user information