Understanding how API routes work in Next.js
Tags: nextjs, react, blog
Published: 2021-09-20 19:42:21

---
title: Understanding how API routes work in Next.js
published: true
description: Understanding how API routes work dynamically in Next.js 
tags: nextjs, react, blog 
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ve7mmqw9606153f7hhon.png
---

Next.js makes it really simple for developers at any skill level to build API's whether with REST or GraphQL. I think GraphQL is really cool but for the purposes of simplicity I will focus on building API routes in REST. If you aren't already familiar with REST, REST stands for **RE**presentational **S**tate **T**ransfer. In short, REST is a type of API that conforms to the design principles of the representational state transfer architectural style. And an API built *correctly* in REST is considered what's called Restful. Check out more readings on REST [here](https://www.markdownguide.org/cheat-sheet/).

At a high level, normally, when building a full stack application, let's say a MERN (MongoDB, Express, React and Node) application you'll probably create some separation between both your client and your server code. You'll probably create some `server` directory and in that directory you'll create a standalone express server that then has a bunch of routes that will perform all of your various CRUD (Create, Read, Update and Delete) operations on your database. Then in your client code you'll make `GET/POSTS/PUT/DELETE` requests to those various routes you've created server-side. Sometimes trying to follow how both the client and server code talk to each other can be really confusing. 

Luckily, Next.js to the rescue 🙌. Next.js reduces this confusion and makes it pretty simple to create API routes that map to a particular file created in the `pages/api` directory structure. Let's walk through it. 

**Quick note:** We won't focus on actually hitting a live database in this article. The main point I want to get across is how simple API's can be built in Next.js. Hopefully with this simple mental model any developer should be able to expand on this information and create more complex applications.

The code for this article can also be found in this [sandbox](https://codesandbox.io/s/understanding-next-js-api-routes-l6icx?file=/pages/index.js)


Let's start by creating a new Next.js application using the following command in your terminal.

```javascript
npx create-next-app
#or
yarn create next-app
``` 

You'll be asked to create a name for the project - just pick something 😎. After all the installation is complete, start the development server by running `npm run dev` or `yarn dev` in your terminal. 

At this point, you should be able to visit `http://localhost:3000` to view your application.

Now that everything is running, let's head over to the `pages/api` directory. Inside of this directory create a new `person` directory. And inside of the `person` directory create two files `index.js` and `[id].js` (we'll touch on this bracket syntax soon). Inside of the `pages` root directory, create another `person` directory with one file named `[id].js` in it. Lastly, in the root of your entire application, create a `data.js` file with the following code:

```javascript
export const data = [
  {
    id: 1,
    firstName: "LeBron",
    middleName: "Raymone",
    lastName: "James",
    age: 36,
  },
  {
    id: 2,
    firstName: "Lil",
    middleName: "Nas",
    lastName: "X",
    age: 22,
  },
  {
    id: 3,
    firstName: "Beyoncé",
    middleName: "Giselle",
    lastName: "Knowles-Carter",
    age: 40,
  },
];
```

Your `pages` directory structure should now look like the following:

```
- pages
  - /api
    - /person
      - [id].js
      - index.js  
  - /person
    - [id].js  
```

Any file inside the folder `pages/api` is automatically mapped to `/api/*` and will be treated as an API endpoint instead of a client-side `page`. Also, no need to worry about your client-side bundle size, these files are server-side bundled and won't increase the code size going to the browser. 

In the `index.js` file you just created in the `person` directory, paste the following snippet into your editor:

```javascript
import { data } from "../../../data";

export default function handler(request, response) {
  const { method } = request;

  if (method === "GET") {
    return response.status(200).json(data);
  }

  if (method === "POST") {
    const { body } = request;
    data.push({ ...body, id: data.length + 1 });
    return response.status(200).json(data);
  }
}
```

Let's break this code down - for an API route to work you need to export a function, that receives two parameters: `request`: an instance of [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage) and `response`: an instance of [http.ServerResponse](https://nodejs.org/api/http.html#http_class_http_serverresponse). Inside of this `request handler` you can handle different HTTP methods in an API route by using `request.method` which determines what HTTP method is being used by the request. In this code snippet, we are expecting either a `GET` or `POST` request. If we receive a `GET` request we will simply send a status of `200` and return the data in json form.  If a `POST` request is received we will add what ever is sent over from the client via the `body` on the request to our array of data. You can think of this as if you were to perform a `create` operation on your database. Once we've completed this operation we will then also return a status of `200` and the current state of the data in json form. 

Now let's head over to `pages/index.js`, you should find a bunch of `jsx` that's been provided by Next to render their custom home page. ERASE ALL OF IT  😈. And replace with the following code snippet:

```javascript
import Link from "next/link";
import { useReducer, useState } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "UPDATE_FIRST_NAME":
      return {
        ...state,
        firstName: action.payload.firstName
      };
    case "UPDATE_MIDDLE_NAME":
      return {
        ...state,
        middleName: action.payload.middleName
      };
    case "UPDATE_LAST_NAME":
      return {
        ...state,
        lastName: action.payload.lastName
      };
    case "UPDATE_AGE":
      return {
        ...state,
        age: action.payload.age
      };
    case "CLEAR":
      return initialState;
    default:
      return state;
  }
}

const initialState = {
  firstName: "",
  middleName: "",
  lastName: "",
  age: ""
};

export default function Home() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const [data, setData] = useState([]);

  const fetchData = async () => {
    const response = await fetch("/api/person");

    if (!response.ok) {
      throw new Error(`Error: ${response.status}`);
    }
    const people = await response.json();
    return setData(people);
  };

  const postData = async () => {
    const response = await fetch("/api/person", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(state)
    });

    if (!response.ok) {
      throw new Error(`Error: ${response.status}`);
    }

    dispatch({ type: "CLEAR" });
    const people = await response.json();
    return setData(people);
  };
  return (
    <div style={{ margin: "0 auto", maxWidth: "400px" }}>
      <div style={{ display: "flex", flexDirection: "column" }}>
        <label htmlFor="firstName">First Name</label>
        <input
          type="text"
          id="firstName"
          value={state.firstName}
          onChange={(e) =>
            dispatch({
              type: "UPDATE_FIRST_NAME",
              payload: { firstName: e.target.value }
            })
          }
        />
        <label htmlFor="middleName">Middle Name</label>
        <input
          type="text"
          id="middleName"
          value={state.middleName}
          onChange={(e) =>
            dispatch({
              type: "UPDATE_MIDDLE_NAME",
              payload: { middleName: e.target.value }
            })
          }
        />
        <label htmlFor="lastName">Last Name</label>
        <input
          type="text"
          id="lastName"
          value={state.lastName}
          onChange={(e) =>
            dispatch({
              type: "UPDATE_LAST_NAME",
              payload: { lastName: e.target.value }
            })
          }
        />
        <label htmlFor="age">Age</label>
        <input
          type="text"
          id="age"
          value={state.age}
          onChange={(e) =>
            dispatch({
              type: "UPDATE_AGE",
              payload: { age: e.target.value }
            })
          }
        />
      </div>
      <div
        style={{ marginTop: "1rem", display: "flex", justifyContent: "center" }}
      >
        <button onClick={fetchData}>FETCH</button>
        <button onClick={postData}>CREATE</button>
      </div>
      <div>Data:</div>
      {data ? <pre>{JSON.stringify(data, null, 4)}</pre> : null}
      {data.length > 0 ? (
        <div style={{ textAlign: "center" }}>
          Click a button to go to individual page
          <div
            style={{
              marginTop: "1rem",
              display: "flex",
              justifyContent: "center"
            }}
          >
            {data.map((person, index) => (
              <Link
                key={index}
                href="/person/[id]"
                as={`/person/${person.id}`}
                passHref
              >
                <span
                  style={{
                    padding: "5px 10px",
                    border: "1px solid black"
                  }}
                >{`${person.firstName} ${person.lastName}`}</span>
              </Link>
            ))}
          </div>
        </div>
      ) : null}
    </div>
  );
}
```

Hopefully at this point you are pretty familiar with what's going on here. It's pretty basic React code. If you need to brush up on your React, head over to the [documentation](https://reactjs.org/). The main things I want to point out here are the `fetchData` and `postData` handlers. You'll notice that they are both performing fetch requests on the `api/person` endpoint that we created previously. As a reminder this is client-side code here so we can fetch just using the absolute path of `api/person`. The same doesn't apply for server-side rendering requests and we will touch on that soon. 

Voilà 👌 - this is the bread and butter of API routes in Next.js. 

**Open up your network tab in the devtools of your browser**. 

When you click the `FETCH` button in the UI, you'll notice a `GET` request is made to `api/person` and the response is the data that we hard-coded.

```json
{
      id: 1,
      firstName: "LeBron",
      middleName: "Raymone",
      lastName: "James",
      age: 36,
    },
    { 
      id: 2, 
      firstName: "Lil", 
      middleName: "Nas", 
      lastName: "X", 
      age: 22 
    },
    {
      id: 3,
      firstName: "Beyoncé",
      middleName: "Giselle",
      lastName: "Knowles-Carter",
      age: 40,
},
```
You'll also notice that a `POST` request is sent if you fill out the form inputs and click the `CREATE` button.

Again, you can imagine that in your API code you're performing some `read` and `create` operations on your database and returning the expected data. For this example I wanted to keep it simple. 

Let's head over to the `pages/person/[id].js` file and paste this snippet into the editor: 

```javascript
import { data } from "../../../data";

export default function handler(request, response) {
  const { method } = request;

  if (method === "GET") {
    const { id } = request.query;

    const person = data.find((person) => person.id.toString() === id);

    if (!person) {
      return response.status(400).json("User not found");
    }

    return response.status(200).json(person);
  }
}
```
You might be wondering, what's up with the bracket syntax? Well, the short of it is Next.js provides a way for developers to create dynamic routing. The text that you put in between the brackets work as a query parameter that you have access to from the browser url. More info on dynamic routes can be found in the [docs](https://nextjs.org/docs/routing/dynamic-routes). Breaking down this snippet above we are expecting a `GET` request that will carry an `id` on the `request.query` object. Once we have access to this `id` we can then search our "database" for a person whose `id` matches the `id` provided by the request. If we find a `person` then we return it in `json` format with a status of `200`. If not, we return an error of `400` with a message `User not found`. However, there's still one more step. **Remember** this is just the `api` step, we still need to render a page for our individual person.

Let's hop over to `person/[id].js` and paste the following code snippet: 

```javascript
import { useRouter } from "next/router";

const Person = ({ user }) => {
  const router = useRouter();

  return (
    <div>
      <button onClick={() => router.back()}>Back</button>
      <pre>{JSON.stringify(user, null, 4)}</pre>
    </div>
  );
};

export async function getServerSideProps(context) {
  const { id } = context.params;
  const user = await fetch(`http://localhost:3000/api/person/${id}`);
  const data = await user.json();

  if (!data) {
    return {
      notFound: true
    };
  }

  return {
    props: { user: data }
  };
}

export default Person;
```

Let's break this down - if we look back at `pages/index.js` you'll find the following snippet:

```javascript
{data.map((person, index) => (
               <Link
                key={index}
                href="/person/[id]"
                as={`/person/${person.id}`}
                passHref
              >
                <span
                  style={{
                    padding: "5px 10px",
                    border: "1px solid black"
                  }}
                >{`${person.firstName} ${person.lastName}`}</span>
              </Link>
))}
```
You'll notice that we are mapping over each person in our `data` and rendering `Link` tags for each of them. Next.js provides [`Link`](https://nextjs.org/docs/api-reference/next/link) tags that can be used for client-side transitions between routes. In our case we are expecting each  `Link` to transition to `person/[id]` page, the `id` being the one provided on each person object. So when the user clicks one of these links, Next.js will transition to the appropriate page, for example `person/2`. 

By default, Next.js pre-renders every page. This means that Next.js will create HTML for each page in advance, instead of having it all done via client-side Javascript. You can pre-render either by [Static Generation](https://nextjs.org/docs/basic-features/pages#static-generation-recommended) or [Server-side rendering](https://nextjs.org/docs/basic-features/pages#server-side-rendering). Since our app relies on "frequently updated data fetched from an external API" we will go the server-side rendering route. 

This leads us back to the `person/[id].js` file. You'll notice that we are exporting an async function called `getServerSideProps`. This is one of helper functions that Next.js provides us for pre-rendering a server-side page. Each request will pre-render a page on each request using the data return back from this function. The `context` parameter is an object that contains useful information that can be used to in this function. In our case we want to get access to the `id` that's been passed in the request using the `context.params` object. More information on the `context` parameter [here](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering). 

Once we have access to the `id` we make a `fetch` request to `http://localhost:3000/api/person${id}`. **Notice** we have to provide the full absolute url including the scheme (http://), host (localhost:) and port (3000). That's because this request is happening on the server not the client. You have to use an absolute URL in the server environment NOT relative. Once the request is successful, we format the data to `json` and check whether we have `data` or not. If not, we return an object with `notFound: true`. This is some Next.js magic that tells the component to render a 404 status page. Otherwise, if we have data we will return a `prop` object which will be passed to the page components as props. In our case we will pass along the `data` we've received from the request to the `Person` component. In our `Person` component, we are destructioning `user` off of the `props` and using it to display. 

And that's pretty much it. There's a ton more detail I could've delved into but hopefully on a high level you now have a better understanding of how Next.js API routes work. 

Connect with me on [Twitter](https://twitter.com/________cody) and on [LinkedIn](https://www.linkedin.com/in/codymjarrett/)