Categories
Examples Next js

How to Create API Routes in Next.js 14

Next.js 14 got some cool stuff like the app router which makes it very different of creating API routes. Although you can use the same old convention but due to the new Server actions, this is going to change forever. This feature was originally introduced from the Next.js 13 and was introduces as an experimental feature.

Writing APIs with Page Router (OLD Method)

Before this app router every file in the pages directory was a route itself. Next.js supports this convention since version 9.4. Which helps to adopt very own style of managing routes and components. So, for example you can have a folder structure like follows.

src
├── components
│   ├── Header.tsx
│   ├── Footer.tsx
│   └── ...
├── pages
│   ├── index.tsx
│   ├── about.tsx
│   ├── api
│   │   └── hello.ts
│   └── ...
├── styles
│   ├── global.css
│   └── ...
└── utils
    └── ...

Code language: CSS (css)

Here is the way of writing API in old style.

export default function handler(req, res) {
  //...
}
Code language: JavaScript (javascript)

In the above code, it may seem similar to Node.js style. But it have some more features. For example, the req object which is the Request object and the res Response object have options like

  • req.body
  • req.query
  • req.cookies
  • res.status
  • res.json
  • res.redirect

If you were developing api’s in TypeScript this would be something like this

// pages/api/hello.ts
import type { NextApiRequest, NextApiResponse } from 'next'

type Data = {
  name: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  res.status(200).json({ name: 'Hello Abdul Rehman!' })
}

Code language: TypeScript (typescript)

OLD Style GET/POST Requests

In the older versions, to differentiate the GET/POST requests what we need to do is to check the req.method property. This property returns a string which we can check for the HTTP method. This could be such as GET, POST, PUT, DELETE, etc. Here is how we use to do it.

import type { NextApiRequest, NextApiResponse } from 'next'
 
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'GET') {
    // Handle GET request
    res.status(200).json({ message: 'Hello from Next.js!' });
  } else if (req.method === 'POST') {
    // Handle POST request
    // You can access the request body as req.body
    res.status(201).json({ message: 'Data received!' });
  } else {
    // Handle other methods
    res.status(405).json({ message: 'Method not allowed' });
  }
}
Code language: TypeScript (typescript)

Or we can use some package like next-connect which make EXPRESS style request. This will be like as follows.

// pages/api/hello.js
import nc from 'next-connect';

const handler = nc()
  .get((req, res) => {
    // Handle GET request
    res.status(200).json({ message: 'Hello from Next.js!' });
  })
  .post((req, res) => {
    // Handle POST request
    // You can access the request body as req.body
    res.status(201).json({ message: 'Data received!' });
  })
  .all((req, res) => {
    // Handle other methods
    res.status(405).json({ message: 'Method not allowed' });
  });

export default handler;

Code language: JavaScript (javascript)

New App Router in Next.js 14

According to the Next.js 14 Release blog post here is what it says.

The App Router is built on the React canary channel, which is stable for frameworks
 to adopt new features. As of v14, Next.js has upgraded to the latest React canary, which includes stable Server Actions.

Next.js 14 | Next.js (nextjs.org)

Here you can see that the api routes are under the api folder inside the pages folder. But with the help of app router, the routes are being changed. The app directory is a new feature introduced in Next.js 13. Now there is a new directory inside your `src` directory. This new directory is called app directory. Inside this app directory you create new folders. Every folder itself is a new route.

Inside this folder you create a page.tsx file or you can create page.jsx file which will be rendered when that page will be accessed. You can still create a pages and API directory as well. Although all the pages are SSR which means they are rendered on Server Side, unless they are specifically told to be rendered on Client Side by specifying the 'use client' directive on the top of the file. This directive tells that that specific component would be rendered on client’s browser. With this new app router you can create following type of routing files.

layout.js .jsx .tsxLayout
page.js .jsx .tsxPage
loading.js .jsx .tsxLoading UI
not-found.js .jsx .tsxNot found UI
error.js .jsx .tsxError UI
global-error.js .jsx .tsxGlobal error UI
route.js .tsAPI endpoint
template.js .jsx .tsxRe-rendered layout
default.js .jsx .tsxParallel route fallback page

As you can see you can create a route.ts file for your api end point here. You can create for a seperate page a seperate route.ts file which will make a seperate end point for that route. For example, if you create a route.ts file under the hello directory inside the app directory. You will be able to access that route with /hello

How creating API Route in Next.js 14 with App Router is different from previous?

Now that you have idea of how the routes were written in previous version with the page router, here is how they are different with app router. With the new version of Next.js 14 the app routers use route.js or route.ts file and we have to write the API with either route file or we can use the reuseable server actions. First of all, let’s see how writing API with App Router which is built on top of React Server Components, is different from Page Router? Here is the quick summary.

  • Now the API are not under the pages/api directory, instead the api end points are of folder name inside the app directory. You can place the api folder inside the app folder. Inside that folder you can place further folders or if you only need one end point you can write the route.ts file just under the /api directory inside the app folder.
  • Now the API routes are server components. Which mean they are just normal React functions returning React Elements. So while returning the response instead of just writing it to res object, you have to return a React Element.
  • API Routes now could be written in new Server Actions which could be reused in multiple end points and could be called directly in client components.

How to create API Routes with App Router in Next.js 14?

With the new changes in the next.js 14 and app router, now each api end point is a route file. Which could be of .js or .ts extention. This will be treated as APIend point. Which is now usual way of creating API in the new Next.js 14. This is the following image from the official documentation page of the next.js 14.

Creating api end point in next.js 14

Here you can clearly see that if you create an api folder inside your app folder. You now have to create a route.js or route.ts file to make an API end point. Which you can fetch('/api') later inside your components.

Now you have to write separate GET and POST methods. Also, you have to return proper element from the API route. Here is the quick example code for writing the GET and POST API end point functions with new APP Router.

GET Route function

import type {NextApiRequest, NextApiResponse} from 'next'

export async function GET (request:NextApiRequest){
    return new NextResponse(JSON.stringify({ message: "Welcome John Doe" }), {
    status: 200,
  });
} 

Code language: JavaScript (javascript)

Using POST Method

import type {NextApiRequest, NextApiResponse} from 'next'

export async function POST(request: NextRequest) {
  const { username}: MyData = await request.json();

  if (!username) {
    return new NextResponse(
      JSON.stringify({ name: "Please provide something to search for" }),
      { status: 400 }
    );
  }
  
  return new NextResponse(JSON.stringify({ answer: "John Doe" }), {
    status: 200,
  });
}Code language: JavaScript (javascript)

Next.js 14 Server actions instead of API

As mentioned above the Next.js 14 introduced the “Server Actions”. With this thing introduced api calling is being different then it was before. Also, as I had mentioned above that every page and component is server rendered by default so you cannot use react hooks on it. Which means if you want to use even the simplest React.useState hook, you have to mention that your component is client rendered. Which you can simply state by mentioning 'use client'; on top of your component. Which we had done in one of our previous blog post, when we were creating a simple nav bar in next.js 14.

Server Actions are reuseable, which means you can create an actions.ts file and inside that file every written server action could be imported into many numbers of client components and directory could be used for form submissions. So, with the help of this, now you do not need to write separate API end points for form submissions. Instead, all of these could be replaced with the Next.js 14 based Server Actions. This is the example server actions which use the sql interactions directly inside the server action functions. This code is taken from this GitHub repository and it simplify things with best example.

import { sql } from '@vercel/postgres';

export async function fetchRevenue() {
  // Add noStore() here prevent the response from being cached.
  // This is equivalent to in fetch(..., {cache: 'no-store'}).

  try {
    // Artificially delay a response for demo purposes.
    // Don't do this in production :)

    // console.log('Fetching revenue data...');
    // await new Promise((resolve) => setTimeout(resolve, 3000));

    const data = await sql<Revenue>`SELECT * FROM revenue`;

    // console.log('Data fetch completed after 3 seconds.');

    return data.rows;
  } catch (error) {
    console.error('Database Error:', error);
    throw new Error('Failed to fetch revenue data.');
  }
}

export async function fetchLatestInvoices() {
  try {
    const data = await sql<LatestInvoiceRaw>`
      SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
      FROM invoices
      JOIN customers ON invoices.customer_id = customers.id
      ORDER BY invoices.date DESC
      LIMIT 5`;

    const latestInvoices = data.rows.map((invoice) => ({
      ...invoice,
      amount: formatCurrency(invoice.amount),
    }));
    return latestInvoices;
  } catch (error) {
    console.error('Database Error:', error);
    throw new Error('Failed to fetch the latest invoices.');
  }
}


export async function fetchInvoicesPages(query: string) {
  try {
    const count = await sql`SELECT COUNT(*)
    FROM invoices
    JOIN customers ON invoices.customer_id = customers.id
    WHERE
      customers.name ILIKE ${`%${query}%`} OR
      customers.email ILIKE ${`%${query}%`} OR
      invoices.amount::text ILIKE ${`%${query}%`} OR
      invoices.date::text ILIKE ${`%${query}%`} OR
      invoices.status ILIKE ${`%${query}%`}
  `;

    const totalPages = Math.ceil(Number(count.rows[0].count) / ITEMS_PER_PAGE);
    return totalPages;
  } catch (error) {
    console.error('Database Error:', error);
    throw new Error('Failed to fetch total number of invoices.');
  }
}

export async function fetchInvoiceById(id: string) {
  try {
    const data = await sql<InvoiceForm>`
      SELECT
        invoices.id,
        invoices.customer_id,
        invoices.amount,
        invoices.status
      FROM invoices
      WHERE invoices.id = ${id};
    `;

    const invoice = data.rows.map((invoice) => ({
      ...invoice,
      // Convert amount from cents to dollars
      amount: invoice.amount / 100,
    }));

    return invoice[0];
  } catch (error) {
    console.error('Database Error:', error);
    throw new Error('Failed to fetch invoice.');
  }
}

Code language: TypeScript (typescript)

By Abdul Rehman

My name is Abdul Rehman and I love to do Reasearch in Embedded Systems, Artificial Intelligence, Computer Vision and Engineering related fields. With 10+ years of experience in Research and Development field in Embedded systems I touched lot of technologies including Web development, and Mobile Application development. Now with the help of Social Presence, I like to share my knowledge and to document everything I learned and still learning.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.