In this tutorial, we'll learn how to build robust APIs using Next.js 14's App Router. We'll create a simple API for managing a book collection to demonstrate key concepts and best practices.
Prerequisites
- Node.js installed on your system
- Basic knowledge of JavaScript/TypeScript
- Familiarity with Next.js fundamentals
Setting Up the Project
First, create a new Next.js project:
npx create-next-app@latest my-api-project
cd my-api-project
Project Structure
Our API will follow the Next.js App Router convention. Create the following structure:
app/
api/
books/
route.ts
books/[id]/
route.ts
Creating the API Routes
1. GET and POST Endpoints (app/api/books/route.ts)
import { NextResponse } from 'next/server'
// In-memory storage for demonstration
let books = [
{ id: 1, title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },
{ id: 2, title: '1984', author: 'George Orwell' }
]
export async function GET() {
return NextResponse.json(books)
}
export async function POST(request: Request) {
try {
const book = await request.json()
// Validate input
if (!book.title || !book.author) {
return NextResponse.json(
{ error: 'Title and author are required' },
{ status: 400 }
)
}
// Create new book
const newBook = {
id: books.length + 1,
title: book.title,
author: book.author
}
books.push(newBook)
return NextResponse.json(newBook, { status: 201 })
} catch (error) {
return NextResponse.json(
{ error: 'Invalid request body' },
{ status: 400 }
)
}
}
2. Single Book Operations (app/api/books/[id]/route.ts)
import { NextResponse } from 'next/server'
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const book = books.find(b => b.id === parseInt(params.id))
if (!book) {
return NextResponse.json(
{ error: 'Book not found' },
{ status: 404 }
)
}
return NextResponse.json(book)
}
export async function PUT(
request: Request,
{ params }: { params: { id: string } }
) {
try {
const bookIndex = books.findIndex(b => b.id === parseInt(params.id))
if (bookIndex === -1) {
return NextResponse.json(
{ error: 'Book not found' },
{ status: 404 }
)
}
const updatedBook = await request.json()
// Validate input
if (!updatedBook.title || !updatedBook.author) {
return NextResponse.json(
{ error: 'Title and author are required' },
{ status: 400 }
)
}
books[bookIndex] = {
...books[bookIndex],
...updatedBook
}
return NextResponse.json(books[bookIndex])
} catch (error) {
return NextResponse.json(
{ error: 'Invalid request body' },
{ status: 400 }
)
}
}
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
const bookIndex = books.findIndex(b => b.id === parseInt(params.id))
if (bookIndex === -1) {
return NextResponse.json(
{ error: 'Book not found' },
{ status: 404 }
)
}
books = books.filter(b => b.id !== parseInt(params.id))
return NextResponse.json(
{ message: 'Book deleted successfully' },
{ status: 200 }
)
}
Testing the API
You can test your API using cURL or tools like Postman. Here are some example requests:
Get All Books
curl http://localhost:3000/api/books
Create a Book
curl -X POST http://localhost:3000/api/books \
-H "Content-Type: application/json" \
-d '{"title": "Dune", "author": "Frank Herbert"}'
Get a Single Book
curl http://localhost:3000/api/books/1
Update a Book
curl -X PUT http://localhost:3000/api/books/1 \
-H "Content-Type: application/json" \
-d '{"title": "Updated Title", "author": "Updated Author"}'
Delete a Book
curl -X DELETE http://localhost:3000/api/books/1
Error Handling and Best Practices
- Input Validation: Always validate incoming request data
- HTTP Status Codes: Use appropriate status codes for different scenarios
- Error Messages: Provide clear error messages for better debugging
- Type Safety: Use TypeScript for better type checking and IDE support
- Response Format: Maintain consistent response format across endpoints
Conclusion
Next.js's App Router provides a powerful and intuitive way to build APIs. The file-based routing system makes it easy to organize your endpoints, while built-in features like automatic response parsing make handling requests straightforward.
Remember to:
- Use appropriate HTTP methods for different operations
- Implement proper error handling
- Validate input data
- Follow REST conventions
- Use TypeScript for better type safety
For production applications, consider:
- Adding authentication/authorization
- Implementing rate limiting
- Setting up a proper database
- Adding request validation middleware
- Implementing logging and monitoring
Related Posts
6 min read
The Model Context Protocol (MCP) has emerged as one of the most significant developments in AI technology in 2025. Launched by Anthropic in November 2024, MCP is an open standard designed to bridge AI...
5 min read
APIs (Application Programming Interfaces) are the backbone of modern digital applications. They allow different software systems to communicate, exchange data, and collaborate seamlessly. As businesse...