Next.js
API Routes
Build server endpoints that handle newsletter signups, article queries, and user data for NewsWave
Traditional React applications run entirely in the browser. The component renders, fetches data from external APIs, and updates the interface based on user interactions. But what happens when you need to process payments, send emails, or store sensitive data? You need a server. Next.js solves this with API Routes — server-side endpoints that run on the same domain as your application. Think of them as mini Express.js routes built directly into your Next.js project. Unlike client-side code that everyone can inspect in the browser, API routes execute on the server where your secrets stay secret. The NewsWave team wants users to subscribe to their newsletter. Building a traditional subscription flow requires setting up a separate backend server, configuring CORS, and managing two different deployments. API Routes eliminate this complexity by letting you write server logic alongside your pages.Creating Your First API Route
API Routes live in a specialpages/api directory. Any file you create here becomes an HTTP endpoint. When someone visits /api/hello, Next.js executes your server code and returns the response.
The magic happens through file-based routing, just like pages. Create pages/api/hello.js and Next.js automatically creates the /api/hello endpoint. This approach feels natural because you already understand how page routing works.
Every API route exports a default function that receives two parameters: req (request) and res (response). The request contains data from the client — form submissions, query parameters, headers. The response lets you send data back — JSON, status codes, cookies.
// pages/api/hello.js — NewsWave's first API endpoint
export default function handler(req, res) {
// req = incoming request data from the browser
// res = outgoing response we send back
res.status(200).json({
message: 'Welcome to NewsWave API',
timestamp: new Date().toISOString()
})
}What just happened?
Next.js automatically created a server endpoint at /api/hello. When you visit this URL, the handler function runs on the server (not in the browser) and returns JSON data. The status(200) indicates success, while json() sets the content type and sends the response.
Building a Newsletter Subscription Endpoint
Real applications need to handle different HTTP methods. A newsletter signup should accept POST requests containing email addresses, validate the data, and store the subscription. API routes handle this by checking the request method and branching logic accordingly. The request object contains everything the client sends. Form data arrives inreq.body, URL parameters in req.query, and HTTP headers in req.headers. The response object provides methods to send different types of responses back to the client.
Think of API routes like a restaurant kitchen. The waiter (browser) brings orders (requests) to the kitchen (API route). The chef (your handler function) processes the order based on what was requested and sends back the finished dish (response). Different orders require different preparation methods.
// pages/api/newsletter.js — Handle NewsWave newsletter signups
export default function handler(req, res) {
// Only accept POST requests for newsletter signup
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' })
}
// Extract email from request body
const { email } = req.body
}// pages/api/newsletter.js — Add email validation
export default function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' })
}
const { email } = req.body
// Validate email format using a simple regex
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!email || !emailRegex.test(email)) {
return res.status(400).json({ error: 'Valid email address required' })
}// pages/api/newsletter.js — Complete newsletter endpoint
export default function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' })
}
const { email } = req.body
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!email || !emailRegex.test(email)) {
return res.status(400).json({ error: 'Valid email address required' })
}
// TODO: Save email to database or email service
console.log(`New newsletter signup: ${email}`)
// Send success response
res.status(201).json({
success: true,
message: 'Successfully subscribed to NewsWave newsletter'
})
}What just happened?
The API route checked the HTTP method, validated the email format, and returned appropriate responses. Status code 405 means "method not allowed", 400 means "bad request", and 201 means "created successfully". This pattern — validate input, process data, return response — forms the foundation of most API endpoints.
Connecting Frontend Forms to API Routes
API routes become powerful when connected to your React components. NewsWave needs a newsletter signup form that posts data to your API endpoint. This creates a seamless full-stack experience where the frontend and backend share the same domain and deployment. Building this connection requires understanding how React's state management integrates with HTTP requests. The form captures user input in local state, validates it client-side for immediate feedback, then sends it to your API route for server-side processing. Unlike external APIs that might have CORS restrictions or authentication complexities, your API routes are part of the same application. This means simpler requests, shared environment variables, and unified error handling.// components/NewsletterForm.js — NewsWave newsletter signup form
import { useState } from 'react'
export default function NewsletterForm() {
// Track form state and submission status
const [email, setEmail] = useState('')
const [status, setStatus] = useState('idle') // idle, submitting, success, error
const [message, setMessage] = useState('')
}fetch function sends a POST request to /api/newsletter with the email data.
// components/NewsletterForm.js — Add form submission handler
const handleSubmit = async (e) => {
e.preventDefault() // Prevent page reload
setStatus('submitting')
try {
// Send POST request to our API route
const response = await fetch('/api/newsletter', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
})
const data = await response.json()
if (response.ok) {
setStatus('success')
setMessage(data.message)
setEmail('') // Clear the form
} else {
setStatus('error')
setMessage(data.error)
}
} catch (error) {
setStatus('error')
setMessage('Network error. Please try again.')
}
}// components/NewsletterForm.js — Complete form component
return (
<div style={{ maxWidth: '400px', margin: '0 auto', padding: '20px' }}>
<h3>Subscribe to NewsWave</h3>
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
disabled={status === 'submitting'}
style={{ width: '100%', padding: '12px', margin: '10px 0' }}
/>
<button
type="submit"
disabled={status === 'submitting'}
style={{
width: '100%', padding: '12px',
background: status === 'submitting' ? '#ccc' : '#0369a1',
color: 'white', border: 'none', borderRadius: '4px'
}}
>
{status === 'submitting' ? 'Subscribing...' : 'Subscribe'}
</button>
</form>
{message && (
<p style={{
color: status === 'success' ? '#15803d' : '#dc2626',
marginTop: '10px'
}}>
{message}
</p>
)}
</div>
)What just happened?
The React form communicates with your API route through a POST request. The component manages loading states, shows success messages, and handles errors gracefully. This creates a smooth user experience where the form feels responsive and provides clear feedback. Try entering an invalid email to see the validation in action.
Dynamic API Routes and Query Parameters
Just like pages, API routes support dynamic segments using square brackets. NewsWave needs an endpoint to fetch individual articles by their slug —/api/articles/breaking-news-update should return that specific article's data.
Dynamic API routes follow the same naming convention as dynamic pages. Create pages/api/articles/[slug].js and Next.js makes the slug available in req.query.slug. This pattern scales to complex hierarchies like /api/users/[id]/posts/[postId].
Query parameters also appear in req.query. A request to /api/articles/tech-news?limit=5&sort=date gives you { slug: 'tech-news', limit: '5', sort: 'date' } in the query object.
// pages/api/articles/[slug].js — Fetch NewsWave articles by slug
export default function handler(req, res) {
const { slug } = req.query // Extract slug from URL
// Mock article database
const articles = {
'breaking-tech-news': {
id: 1,
title: 'Revolutionary AI Breakthrough Announced',
author: 'Sarah Chen',
category: 'Tech',
views: 15420
},
'market-update-today': {
id: 2,
title: 'Stock Markets Rally on Economic Data',
author: 'Michael Rodriguez',
category: 'Business',
views: 8932
}
}// pages/api/articles/[slug].js — Complete article lookup
export default function handler(req, res) {
const { slug } = req.query
const articles = {
'breaking-tech-news': {
id: 1,
title: 'Revolutionary AI Breakthrough Announced',
author: 'Sarah Chen',
category: 'Tech',
views: 15420
},
'market-update-today': {
id: 2,
title: 'Stock Markets Rally on Economic Data',
author: 'Michael Rodriguez',
category: 'Business',
views: 8932
}
}
const article = articles[slug]
if (!article) {
return res.status(404).json({ error: 'Article not found' })
}
// Return the article data
res.status(200).json(article)
}What just happened?
The dynamic route captured the slug from the URL and used it to lookup article data. When the article exists, it returns the data with a 200 status. When it doesn't exist, it returns a 404 error. This pattern works for any dynamic data lookup — users, products, categories.
Error Handling and Status Codes
Professional APIs communicate clearly through HTTP status codes and consistent error formats. Status codes tell clients exactly what happened — success, client error, server error. Consistent error responses help frontend developers build reliable error handling. The 2xx range indicates success: 200 for general success, 201 for resource creation, 204 for successful deletion with no content. The 4xx range indicates client errors: 400 for bad requests, 401 for authentication required, 404 for not found, 405 for wrong HTTP method. The 5xx range indicates server errors: 500 for internal errors, 503 for service unavailable. Error responses should include helpful messages that explain what went wrong and potentially how to fix it. But be careful not to expose sensitive information like database errors or system paths in production error messages.// pages/api/contact.js — NewsWave contact form with proper error handling
export default function handler(req, res) {
try {
// Only accept POST requests
if (req.method !== 'POST') {
return res.status(405).json({
error: 'Method not allowed',
message: 'This endpoint only accepts POST requests'
})
}
const { name, email, message } = req.body // Validate required fields
if (!name || name.trim().length < 2) {
return res.status(400).json({
error: 'Validation failed',
message: 'Name must be at least 2 characters long'
})
}
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return res.status(400).json({
error: 'Validation failed',
message: 'Please provide a valid email address'
})
}
if (!message || message.trim().length < 10) {
return res.status(400).json({
error: 'Validation failed',
message: 'Message must be at least 10 characters long'
})
} // Process the contact form (would save to database/send email)
console.log('New contact form submission:', { name, email, message })
// Return success response
res.status(201).json({
success: true,
message: 'Thank you for contacting NewsWave. We will respond within 24 hours.'
})
} catch (error) {
// Handle unexpected server errors
console.error('Contact form error:', error)
res.status(500).json({
error: 'Internal server error',
message: 'Something went wrong. Please try again later.'
})
}
}What just happened?
The API route validates each field separately and returns specific error messages. The try-catch block handles unexpected errors gracefully. Status codes tell the client exactly what type of error occurred, while the error messages provide actionable feedback for fixing the request.
Understanding API Routes vs External APIs
API Routes differ fundamentally from external APIs in deployment, security, and data flow. Understanding these differences helps you decide when to build your own endpoints versus integrating with third-party services. External APIs run on different servers, require network requests across the internet, and often need API keys or authentication tokens. They might have rate limits, downtime, or versioning changes that affect your application. But they provide specialized functionality you don't want to build yourself — payment processing, email delivery, social media integration. API Routes run on the same server as your Next.js application. They share environment variables, database connections, and deployment pipelines. This tight integration enables powerful patterns like server-side authentication, data aggregation from multiple sources, or custom business logic that external APIs can't provide.API Routes (Internal)
- Same domain and deployment
- Share environment variables
- No CORS restrictions
- Custom business logic
- Database access
External APIs
- Third-party services
- API keys and authentication
- Rate limits and quotas
- Specialized functionality
- Network latency
Production Considerations and Best Practices
Production API routes require additional considerations around security, performance, and monitoring. Unlike development where you control all requests, production APIs face real users, potential abuse, and scaling challenges. Rate limiting prevents abuse by restricting how many requests a single IP address can make within a time window. Without rate limiting, a single user could overwhelm your server with thousands of newsletter signup attempts. Most production applications implement rate limiting at the infrastructure level, but you can add basic protection directly in your API routes. Input sanitization protects against injection attacks where malicious users send harmful data in request bodies. Always validate and sanitize user input before processing it, especially before database queries or email sending.Security Checklist
Validate all inputs: Check types, formats, and ranges before processing
Use HTTPS: Encrypt all data in transit with SSL certificates
Sanitize outputs: Escape user content before displaying in responses
Environment variables: Store secrets in env files, never in code
Quiz
1. How does NewsWave create the endpoint /api/newsletter in Next.js?
2. In the API route pages/api/articles/[slug].js, how do you access the article slug from the URL?
3. What should NewsWave's newsletter API return when someone submits an invalid email format?
Up Next: Data Fetching Basics
Learn how to fetch data from your API routes and external services using getStaticProps and getServerSideProps for optimal performance.