Writing API Routes in Nextjs14

Simeon Nortey
5 min readJul 6, 2024

--

As a software engineer, change can sometimes be challenging. I love using react in its raw form because I love having control over my project. Using Nextjs has taught me that it’s never about how you arrive at the solution but if you have the solution that works. If you are coming from a MERN stack or have a simple understanding of expressJS then this will be very easy for you. Okay so enough talk, let’s get started.

In creating a new NextJS application, just follow these instructions here on this page.

Great, now that we have set up our application, let’s create a simple authenticated application. let’s create a file named middleware.ts in the app folder.

In your middleware.ts let’s create a simple middleware.

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
return NextResponse.next();
}
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|images|icons|scripts).*)",
],
};

This is the middleware function that will run for every matched request. Currently, it just calls NextResponse.next(), which means the request will continue to the next handler without any modifications. All requests will go through.

Now, we will create our API folder under the app folder. So the API folder is going to keep all our routes. Your relative path should look like

src/app/api/login/route.ts

Now let's make sure our API is working. Copy the code below and paste it into the file.

import { NextRequest, NextResponse } from "next/server";

//req is short for request
export async function GET(req: NextRequest) {
return NextResponse.json(
{ message: "this is a get request" },
{ status: 200 }
);
}

export async function POST(req: NextRequest) {
return NextResponse.json(
{ message: "This is a post request" },
{ status: 200 }
);
}

export async function PATCH(req: NextRequest) {
return NextResponse.json(
{ message: "This is a patch request" },
{ status: 200 }
);
}
// you can also handle PATCH, DELETE, PUT
Result for GET request on http://localhost:3000/api/login

For making request or testing requests, I like to use Postman or Curl. Now that is it. That is how to make an API in NextJS. Now let’s make this interesting and add an actual authentication route.

Now outside our app folder, we can have our config folder where we can set up our database and whatnot for authentication. Now please note this, when it comes to authentication Nextjs has so many ways of handling this but in this article, I am going to go through one way of how you handle your authentication.

Since we are going to handle our own JWT (JSON Web Token), install jsonwebtoken

npm install jsonwebtoken
#or
yarn add jsonwebtoken

Add this to your configuration

import jwt, { JwtPayload } from "jsonwebtoken";
import { NextRequest } from "next/server";

const JWT_SECRET = "your_secret_key_goes_here";

export interface DecodeAccessToken extends JwtPayload {
id?: string;
}

export const generateAccessToken = (userId: string) => {
if (JWT_SECRET) {
const tokenPayload = {
userId,
createdAt: Date.now(),
};

const accessToken = jwt.sign(tokenPayload, JWT_SECRET, {
expiresIn: "1h",
});
return accessToken;
}
};

export const decodeAccessToken = (
req: NextRequest
): DecodeAccessToken | null => {
if (JWT_SECRET) {
try {
const access_token = req.nextUrl.searchParams.get("access_token");
const authorization = req.headers.get("authorization");
const accessToken = (access_token as string) || authorization || "";
const decodedToken = jwt.verify(
accessToken,
JWT_SECRET
) as DecodeAccessToken;
return decodedToken;
} catch (error) {
return null;
}
}
return null;
};

Your token payload could be anything you want to encrypt. Normally we encrypt a user ID but don’t put sensitive data in there because these access_tokens can be unencrypted if your jwt_secret is known.

Now in your sign-in post request, we can have something like this. Please note that this is the bare minimum so you would have to handle all other aspects of your sign-in.

import { signin } from "@/config";
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
try {
const body = await req.json();
const { email, password } = body;

const accessToken = await signin({ email, password });

return NextResponse.json(
{ message: "login successful", accessToken },
{ status: 200 }
);
} catch (error) {
console.log("error", error);
return NextResponse.json({ message: "error" }, { status: 500 });
}
}

Now you can choose to handle the verification of the access_token either in the middleware or in the API route function. An example of how to handle your own middleware can be found below.

import { decodeAccessToken } from "@/config";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

const checkPath = (request: NextRequest) => {
const authRoute = ["/api/user", "/api/members"].includes(
request.nextUrl.pathname
);
if (authRoute) {
const isAuth = decodeAccessToken(request);
if (!isAuth) {
return NextResponse.json({ message: "unauthorized" }, { status: 401 });
}
}
return NextResponse.next();
};

export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/api")) {
return checkPath(request);
}
return NextResponse.next();
}
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|images|icons|scripts).*)",
],
};

In the example above, We first check if the pathname starts with api . Then we also check if this route is authenticated. Now here is the thing, you can also do this in the route function itself. Again, it’s up to you. Do what works for you. You can have sub-middleware functions that handle different levels of authentication. I would always recommend to keep your middleware clean.

Another way of handling authentication is using NextAuth.js. You can read more about it here

Writing APIs may not always be necessary, particularly if you do not need to interface with other applications. You may use the use serverdirective to handle server operations without writing APIs in NextJS. As I always say, make something awesome for you and your team. 😊

--

--

Simeon Nortey
Simeon Nortey

Written by Simeon Nortey

Software developer with love for puzzles and solving problems

Responses (1)