SyntaxStudy
Sign Up
Next.js Handling Forms, Validation, and CORS in Route Handlers
Next.js Beginner 1 min read

Handling Forms, Validation, and CORS in Route Handlers

Route Handlers can process form submissions by reading request.formData(). This is useful for file uploads via multipart/form-data or for HTML form POSTs from non-JavaScript clients. For JSON APIs, request.json() parses the body and returns the parsed value. You should always validate incoming data before processing it — libraries like Zod work well in Route Handlers and provide type-safe validation with descriptive error messages. CORS headers must be explicitly set when your Route Handler needs to be called from a different origin, such as a mobile app or a separate front-end domain. You can set CORS headers directly on the Response object or create a helper function that adds the appropriate Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers headers. Handle the OPTIONS preflight request by returning a 200 response with the CORS headers. Rate limiting in Route Handlers can be implemented using the Upstash Rate Limit library, which uses Redis under the hood and is compatible with the Edge Runtime. You check the rate limit at the start of each handler using the client's IP address (available from the x-forwarded-for header or request.ip). If the limit is exceeded you return a 429 Too Many Requests response with a Retry-After header.
Example
// app/api/contact/route.ts — form handling with Zod validation

import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';

const ContactSchema = z.object({
  name: z.string().min(2, 'Name must be at least 2 characters'),
  email: z.string().email('Invalid email address'),
  message: z.string().min(10, 'Message must be at least 10 characters'),
});

// CORS preflight
export async function OPTIONS() {
  return new Response(null, {
    status: 204,
    headers: {
      'Access-Control-Allow-Origin': 'https://myapp.com',
      'Access-Control-Allow-Methods': 'POST, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    },
  });
}

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

  // Validate with Zod
  const result = ContactSchema.safeParse(body);
  if (!result.success) {
    return NextResponse.json(
      { errors: result.error.flatten().fieldErrors },
      { status: 422 },
    );
  }

  const { name, email, message } = result.data;

  // Send email, save to DB, etc.
  await sendContactEmail({ name, email, message });

  return NextResponse.json(
    { success: true, message: 'Message sent' },
    {
      headers: { 'Access-Control-Allow-Origin': 'https://myapp.com' },
    },
  );
}