Next.js (work in progress)
Next.js - 3. API routes
Updated:
What you'll learn
In this article you will learn the following:
- How to create API routes in Next.js
- How to respond to different HTTP methods and requests
- How to use API routes inside of Next.js pages
- How to create dynamic API routes
API routes
API routes allow you to build APIs with Next.js.
If you open pages/api/hello.js
you will see the following:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
Start up the Next.js dev server with:
npm run dev
Then open http://localhost:3000/api/hello in your browser. You should see the following:
Since we have a file called hello.js
inside of pages/api
Next.js will create a route for this API at /api/hello
. API routes can also be dynamic just like dynamic pages which we covered in the previous lesson. We will cover how to create dynamic API routes later in this article.
Code breakdown
Let’s take a deeper look into the hello.js
file.
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
This file contains a single exported function called handler()
. The name of this function is not important, you can name it whatever you want, but handler is a common convention. The function has two parameters req
and res
.
- The
req
parameter is an instance of http.IncomingMessage from Node.js - The
res
parameter is an instance of http.ServerResponse from Node.js
Next.js also provides some built in middlewares and helper functions to make it easier to work with the request and response objects.
The body of our handler()
function contains the following:
res.status(200).json({ name: 'John Doe' })
This returns a response status code of 200 and then it returns a JSON object with a name property and “John Doe” as its value.
HTTP request methods
Our handler method will currently respond to any HTTP request method, but what if we want to have different responses for different requests? We can do this by detecting which type of HTTP method our API route is receiving using the req
object.
Let’s update our handler()
function to only respond to HTTP Get requests.
export default function handler(req, res) {
if (req.method === 'GET') {
res.status(200).json({ name: 'John Doe' })
}
}
By using a simple if()
statement we can detect different HTTP request methods our API route receives and respond accordingly.
Let’s update our handler()
to throw a 400 status code if it receives any HTTP method other than GET.
export default function handler(req, res) {
if (req.method === 'GET') {
res.status(200).json({ name: 'John Doe' })
} else {
res.status(400).json({ error: 'Your request could not be processed.' })
}
}
API routes & getStaticProps
Now that we have a better understanding of API routes, let’s see how we can use them inside of Next.js pages. Inside of our pages/posts/[id].js
we are making an API call to an external API to get our post data.
export async function getStaticProps({ params }) {
const res = await fetch(
`https://jsonplaceholder.typicode.com/posts/${params.id}`
)
const post = await res.json()
console.log(post)
return { props: { post } }
}
Let’s create a new API route for our posts and use that inside of the getStaticProps()
function.
Posts API route
First, create a new file called posts.js
inside of /pages/api
like so.
Next, we will create our handler()
function.
export default function handler(req, res) {}
Next we will need to return our posts data. To do this, we will copy the JSON from https://jsonplaceholder.typicode.com/posts into a new file and use that inside of our handler()
.
Create a new folder called data
at the root of our Next.js project. Then create a file called posts.json
inside of it.
Copy and paste all of the posts from https://jsonplaceholder.typicode.com/posts into the posts.json
file.
Next, we will need to import this file into our pages/api/posts.js
file like so:
import posts from '../../data/posts.json'
export default function handler(req, res) {}
Now all we have to do is return our posts from our handler()
function like so:
import posts from '../../data/posts.json'
export default function handler(req, res) {
res.status(200).json(posts)
}
Now if you go to the URL http://localhost:3000/api/posts in your web browser you should see the following:
Now that we have our API route returning our posts, we need to update the getStaticProps()
and getStaticPaths()
functions to our API route like so:
Here is the entire contents of pages/posts/[id].js
function Post({ post }) {
return (
<>
<h1>{post.title}</h1>
<p>{post.body}</p>
</>
)
}
export async function getStaticPaths() {
const res = await fetch('http://localhost:3000/api/posts')
const posts = await res.json()
const paths = posts.map((post) => ({
params: { id: post.id.toString() },
}))
return { paths, fallback: false }
}
export async function getStaticProps({ params }) {
const res = await fetch(`http://localhost:3000/api/posts/${params.id}`)
const post = await res.json()
console.log(post)
return { props: { post } }
}
export default Post
You should be getting an error.
Let’s try and get one of our posts by visiting http://localhost:3000/posts/1 you should see the following error:
So what exactly is going on here? Well, if you look closely, inside of getStaticProps()
we are trying to fetch each post by an ID number:
const res = await fetch(`http://localhost:3000/api/posts/${params.id}`)
The problem is that we only created an API route for /api/posts
not for /api/posts/1
. In order for this to work, we need to create a dynamic API route.
Dynamic API routes
Create a folder called posts
inside of /pages/api
like so:
Next, move /pages/api/posts.js
inside of the /pages/api/posts
folder and rename it to index.js
Now create a file named /api/posts/[id].js
like so:
Next copy and paste the following into pages/api/[id].js
.
import posts from '../../../data/posts.json'
export default function handler(req, res) {
res.status(200).json(posts)
}
Now if you go to the URL http://localhost:3000/api/posts/1 you should see the following:
Our dynamic API route is currently returning all of the posts data instead of just a single post with the ID of 1. Let’s write some logic inside of our handler()
function to filter this data and return the correct post based upon the ID in the URL.
The first thing we will need to do is get the ID number from the request.
export default function handler(req, res) {
const { id } = req.query
res.status(200).json(posts)
}
We are using destructuring to grab the ID off of the request object.
const { id } = req.query
We can verify this by passing the id
variable into our res
like so:
export default function handler(req, res) {
const { id } = req.query
res.status(200).json(id)
}
Now if you visit http://localhost:3000/api/posts/1 you should see the following:
Now that we are getting the ID from the response we can use it to filter through all of the posts data and return the post with the correct ID.
export default function handler(req, res) {
const { id } = req.query
const post = posts.filter((post) => id == post.id)
res.status(200).json(post)
}
Now when we visit http://localhost:3000/api/posts/1 we should see only the post data for that ID.
Each time you change the ID in the URL you should get back a different post.
Now if you try to visit http://localhost:3000/posts/1 you will notice that the screen is blank!?! If you look closely at the JSON data returned from each post, you will see that the post data is nested inside of an array.
Inside of pages/posts/[id].js
at the top of our file, our Post
function is the following:
function Post({ post }) {
return (
<>
<h1>{post.title}</h1>
<p>{post.body}</p>
</>
)
}
We are not expecting the data to be nested inside of an array which is why the post is not being displayed. We have two options to fix this.
First option is to update the Post()
like so:
function Post({ post }) {
console.log(post)
return (
<>
<h1>{post[0].title}</h1>
<p>{post[0].body}</p>
</>
)
}
The second option and better option in my opinion is to use destructuring again. Update the getStaticProps()
to the following:
function Post({ post }) {
console.log(post)
return (
<>
<h1>{post.title}</h1>
<p>{post.body}</p>
</>
)
}
// ...
export async function getStaticProps({ params }) {
const res = await fetch(`http://localhost:3000/api/posts/${params.id}`)
const [post] = await res.json() // this is destructuring
return { props: { post } }
}
<aside> 💡 Notice that we do not need to update anything within the Post
function at the top, since we are now sending it the correct data.
</aside>
On this line here:
const [post] = await res.json()
we are destructuring the post object from the array it is nested inside.
Wrap up
In this article you learned the following:
- How to create API routes in Next.js
- How to respond to different HTTP methods and requests
- How to use API routes inside of Next.js pages
- How to create dynamic API routes