Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Get Post Handlers
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

GET and POST Request Handlers

Building CRUD APIs with Route Handlers

GET and POST are the most common HTTP methods. GET retrieves data using query parameters for filtering, while POST creates resources by sending data in the request body. Master these patterns and you'll build RESTful APIs that follow web standards, handle edge cases gracefully, and provide excellent developer experience. Let's build production-ready GET and POST handlers!

GET Request Handlers

Basic GET Handler

app/api/posts/route.ts
export async function GET(request: Request) {
  // Fetch all posts
  const posts = await db.posts.findMany({
    orderBy: { createdAt: 'desc' },
  });

  return Response.json(posts);
}

// GET /api/posts
// Returns: [{ id: 1, title: '...', ... }, ...]

// ✅ Fetches data from database
// ✅ Returns JSON array
// ✅ No query parameters needed for simple list

GET with Query Parameters

app/api/posts/route.ts
export async function GET(request: Request) {
  // Parse URL to get query parameters
  const { searchParams } = new URL(request.url);
  
  // Extract parameters
  const page = parseInt(searchParams.get('page') || '1', 10);
  const limit = parseInt(searchParams.get('limit') || '10', 10);
  const category = searchParams.get('category');
  const search = searchParams.get('search');

  // Build query
  const where: any = {};
  
  if (category) {
    where.category = category;
  }
  
  if (search) {
    where.title = {
      contains: search,
      mode: 'insensitive',
    };
  }

  // Fetch with pagination
  const posts = await db.posts.findMany({
    where,
    skip: (page - 1) * limit,
    take: limit,
    orderBy: { createdAt: 'desc' },
  });

  // Get total count for pagination
  const total = await db.posts.count({ where });

  return Response.json({
    posts,
    pagination: {
      page,
      limit,
      total,
      totalPages: Math.ceil(total / limit),
    },
  });
}

// GET /api/posts?page=2&limit=20&category=tech&search=nextjs
// ✅ Pagination support
// ✅ Category filtering
// ✅ Search functionality
// ✅ Total count for pagination metadata

GET with Multiple Query Parameters

app/api/search/route.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);

  // Multiple values for same parameter
  // GET /api/search?tag=react&tag=nextjs&tag=typescript
  const tags = searchParams.getAll('tag');
  
  // Price range
  const minPrice = searchParams.get('minPrice');
  const maxPrice = searchParams.get('maxPrice');
  
  // Sorting
  const sortBy = searchParams.get('sortBy') || 'createdAt';
  const sortOrder = searchParams.get('sortOrder') || 'desc';

  // Build query
  const where: any = {};

  if (tags.length > 0) {
    where.tags = {
      hasSome: tags,
    };
  }

  if (minPrice || maxPrice) {
    where.price = {};
    if (minPrice) where.price.gte = parseFloat(minPrice);
    if (maxPrice) where.price.lte = parseFloat(maxPrice);
  }

  // Fetch products
  const products = await db.products.findMany({
    where,
    orderBy: { [sortBy]: sortOrder },
  });

  return Response.json({
    products,
    filters: {
      tags,
      minPrice,
      maxPrice,
      sortBy,
      sortOrder,
    },
  });
}

// GET /api/search?tag=react&tag=nextjs&minPrice=10&maxPrice=100&sortBy=price&sortOrder=asc
// ✅ Multiple values with getAll()
// ✅ Range filtering
// ✅ Dynamic sorting
// ✅ Return applied filters

GET with Conditional Response

app/api/user/route.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const userId = searchParams.get('id');

  if (!userId) {
    return Response.json(
      { error: 'User ID is required' },
      { status: 400 }
    );
  }

  const user = await db.users.findUnique({
    where: { id: userId },
    select: {
      id: true,
      name: true,
      email: true,
      // Don't expose password!
    },
  });

  if (!user) {
    return Response.json(
      { error: 'User not found' },
      { status: 404 }
    );
  }

  return Response.json(user);
}

// GET /api/user?id=123
// ✅ Validate required parameters
// ✅ Return 404 if not found
// ✅ Return 400 for invalid input
// ✅ Select only safe fields

POST Request Handlers

Basic POST Handler

app/api/posts/route.ts
export async function POST(request: Request) {
  // Parse JSON body
  const body = await request.json();

  // Create post
  const post = await db.posts.create({
    data: {
      title: body.title,
      content: body.content,
      authorId: body.authorId,
    },
  });

  // Return created resource with 201 status
  return Response.json(post, { status: 201 });
}

// POST /api/posts
// Body: { "title": "My Post", "content": "...", "authorId": "123" }
// Returns: { "id": 1, "title": "My Post", ... } with status 201

// ✅ Parse request body
// ✅ Create resource
// ✅ Return 201 Created
// ✅ Include created resource in response

POST with Validation

app/api/posts/route.ts
import { z } from 'zod';

// Define validation schema
const createPostSchema = z.object({
  title: z.string().min(1, 'Title is required').max(200),
  content: z.string().min(10, 'Content must be at least 10 characters'),
  authorId: z.string().uuid('Invalid author ID'),
  tags: z.array(z.string()).optional(),
  published: z.boolean().default(false),
});

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

  // Validate
  const result = createPostSchema.safeParse(body);

  if (!result.success) {
    return Response.json(
      { 
        error: 'Validation failed',
        details: result.error.flatten().fieldErrors,
      },
      { status: 400 }
    );
  }

  // Type-safe validated data
  const validatedData = result.data;

  // Create post
  const post = await db.posts.create({
    data: validatedData,
  });

  return Response.json(post, { status: 201 });
}

// POST /api/posts
// Body: { "title": "", "content": "short" }
// Returns: 
// {
//   "error": "Validation failed",
//   "details": {
//     "title": ["Title is required"],
//     "content": ["Content must be at least 10 characters"]
//   }
// } with status 400

// ✅ Zod validation
// ✅ Type-safe data
// ✅ Detailed error messages
// ✅ Field-specific errors

POST with Nested Data

app/api/orders/route.ts
import { z } from 'zod';

const createOrderSchema = z.object({
  userId: z.string().uuid(),
  items: z.array(z.object({
    productId: z.string().uuid(),
    quantity: z.number().int().positive(),
    price: z.number().positive(),
  })).min(1, 'At least one item required'),
  shippingAddress: z.object({
    street: z.string(),
    city: z.string(),
    zipCode: z.string(),
    country: z.string(),
  }),
  paymentMethod: z.enum(['credit_card', 'paypal', 'bank_transfer']),
});

export async function POST(request: Request) {
  const body = await request.json();
  const result = createOrderSchema.safeParse(body);

  if (!result.success) {
    return Response.json(
      { error: 'Validation failed', details: result.error.flatten() },
      { status: 400 }
    );
  }

  const data = result.data;

  // Calculate total
  const total = data.items.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0
  );

  // Create order with nested relations
  const order = await db.orders.create({
    data: {
      userId: data.userId,
      total,
      status: 'pending',
      paymentMethod: data.paymentMethod,
      shippingAddress: {
        create: data.shippingAddress,
      },
      items: {
        create: data.items,
      },
    },
    include: {
      items: true,
      shippingAddress: true,
    },
  });

  return Response.json(order, { status: 201 });
}

// POST /api/orders
// Body: {
//   "userId": "...",
//   "items": [{ "productId": "...", "quantity": 2, "price": 29.99 }],
//   "shippingAddress": { "street": "...", ... },
//   "paymentMethod": "credit_card"
// }

// ✅ Nested data validation
// ✅ Complex schema
// ✅ Nested database creation
// ✅ Include related data

POST with File Upload

app/api/upload/route.ts
import { writeFile } from 'fs/promises';
import { join } from 'path';

export async function POST(request: Request) {
  // Parse form data
  const formData = await request.formData();

  const file = formData.get('file') as File;
  const title = formData.get('title') as string;
  const description = formData.get('description') as string;

  if (!file) {
    return Response.json(
      { error: 'No file uploaded' },
      { status: 400 }
    );
  }

  // Validate file type
  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
  if (!allowedTypes.includes(file.type)) {
    return Response.json(
      { error: 'Invalid file type. Only JPEG, PNG, and WebP allowed' },
      { status: 400 }
    );
  }

  // Validate file size (5MB max)
  const maxSize = 5 * 1024 * 1024;
  if (file.size > maxSize) {
    return Response.json(
      { error: 'File too large. Maximum size is 5MB' },
      { status: 400 }
    );
  }

  // Convert to buffer
  const bytes = await file.arrayBuffer();
  const buffer = Buffer.from(bytes);

  // Generate unique filename
  const filename = `${Date.now()}-${file.name}`;
  const filepath = join(process.cwd(), 'public', 'uploads', filename);

  // Save file
  await writeFile(filepath, buffer);

  // Save metadata to database
  const upload = await db.uploads.create({
    data: {
      filename,
      originalName: file.name,
      mimeType: file.type,
      size: file.size,
      title,
      description,
      url: `/uploads/${filename}`,
    },
  });

  return Response.json(upload, { status: 201 });
}

// POST /api/upload (multipart/form-data)
// FormData:
//   file: [File object]
//   title: "My Image"
//   description: "Description"

// ✅ FormData parsing
// ✅ File type validation
// ✅ File size validation
// ✅ Save to disk
// ✅ Store metadata in database

Combining GET and POST

Complete CRUD Endpoint

app/api/posts/route.ts
import { z } from 'zod';
import { NextResponse } from 'next/server';

const createPostSchema = z.object({
  title: z.string().min(1).max(200),
  content: z.string().min(10),
  published: z.boolean().default(false),
});

// GET - List all posts with filtering
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);

  const page = parseInt(searchParams.get('page') || '1', 10);
  const limit = parseInt(searchParams.get('limit') || '10', 10);
  const published = searchParams.get('published');
  const search = searchParams.get('search');

  // Build where clause
  const where: any = {};
  
  if (published !== null) {
    where.published = published === 'true';
  }
  
  if (search) {
    where.OR = [
      { title: { contains: search, mode: 'insensitive' } },
      { content: { contains: search, mode: 'insensitive' } },
    ];
  }

  try {
    const [posts, total] = await Promise.all([
      db.posts.findMany({
        where,
        skip: (page - 1) * limit,
        take: limit,
        orderBy: { createdAt: 'desc' },
        include: {
          author: {
            select: { id: true, name: true },
          },
        },
      }),
      db.posts.count({ where }),
    ]);

    return NextResponse.json({
      posts,
      pagination: {
        page,
        limit,
        total,
        totalPages: Math.ceil(total / limit),
      },
    });
  } catch (error) {
    console.error('Failed to fetch posts:', error);
    return NextResponse.json(
      { error: 'Failed to fetch posts' },
      { status: 500 }
    );
  }
}

// POST - Create new post
export async function POST(request: Request) {
  try {
    const body = await request.json();
    const result = createPostSchema.safeParse(body);

    if (!result.success) {
      return NextResponse.json(
        {
          error: 'Validation failed',
          details: result.error.flatten().fieldErrors,
        },
        { status: 400 }
      );
    }

    const post = await db.posts.create({
      data: {
        ...result.data,
        authorId: 'user-id', // In production, get from auth
      },
      include: {
        author: {
          select: { id: true, name: true },
        },
      },
    });

    return NextResponse.json(post, { status: 201 });
  } catch (error) {
    console.error('Failed to create post:', error);
    return NextResponse.json(
      { error: 'Failed to create post' },
      { status: 500 }
    );
  }
}

// ✅ GET with pagination, filtering, search
// ✅ POST with validation
// ✅ Error handling for both
// ✅ Type-safe responses
// ✅ Include related data

Response Patterns

Success Response Pattern

TYPESCRIPT
// Pattern 1: Simple success
export async function POST(request: Request) {
  const post = await createPost(data);
  return Response.json(post, { status: 201 });
}

// Pattern 2: Success with metadata
export async function GET(request: Request) {
  const posts = await getPosts();
  return Response.json({
    success: true,
    data: posts,
    timestamp: new Date().toISOString(),
  });
}

// Pattern 3: Success with pagination
export async function GET(request: Request) {
  return Response.json({
    data: posts,
    pagination: {
      page: 1,
      limit: 10,
      total: 100,
      totalPages: 10,
    },
  });
}

// Choose pattern based on API needs

Error Response Pattern

TYPESCRIPT
// Pattern 1: Simple error
return Response.json(
  { error: 'Resource not found' },
  { status: 404 }
);

// Pattern 2: Error with details
return Response.json(
  {
    error: 'Validation failed',
    message: 'The provided data is invalid',
    details: {
      title: ['Title is required'],
      email: ['Invalid email format'],
    },
  },
  { status: 400 }
);

// Pattern 3: Error with code
return Response.json(
  {
    error: {
      code: 'INVALID_CREDENTIALS',
      message: 'Invalid email or password',
      timestamp: new Date().toISOString(),
    },
  },
  { status: 401 }
);

// Consistent error format helps clients

CORS Headers

app/api/public/route.ts
export async function GET(request: Request) {
  const data = { message: 'Public API' };

  return new Response(JSON.stringify(data), {
    status: 200,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    },
  });
}

// Handle preflight OPTIONS request
export async function OPTIONS(request: Request) {
  return new Response(null, {
    status: 204,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    },
  });
}

// ✅ CORS for public APIs
// ✅ OPTIONS for preflight
// ✅ Allow specific origins in production

GET and POST API Structure

Organization of GET and POST handlers

appImportant

Select a file or folder to see details

GET and POST Best Practices

1. Validate All Input

TYPESCRIPT
// ✅ GOOD: Validate with Zod
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  age: z.number().int().min(18),
});

export async function POST(request: Request) {
  const body = await request.json();
  const result = schema.safeParse(body);
  
  if (!result.success) {
    return Response.json(
      { errors: result.error.flatten() },
      { status: 400 }
    );
  }
  
  // Use validated result.data
}

// ❌ BAD: No validation
export async function POST(request: Request) {
  const body = await request.json();
  // Directly use body.email, body.age - unsafe!
}

2. Use Proper Status Codes

TYPESCRIPT
// ✅ GOOD: Meaningful status codes
// 200 - GET success
return Response.json(posts);

// 201 - POST created resource
return Response.json(post, { status: 201 });

// 400 - Bad request (validation error)
return Response.json({ error: 'Invalid data' }, { status: 400 });

// 404 - Not found
return Response.json({ error: 'Not found' }, { status: 404 });

// 500 - Server error
return Response.json({ error: 'Server error' }, { status: 500 });

// Status codes communicate meaning to clients

3. Paginate Large Datasets

TYPESCRIPT
// ✅ GOOD: Always paginate
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const page = parseInt(searchParams.get('page') || '1');
  const limit = Math.min(parseInt(searchParams.get('limit') || '10'), 100);
  
  const items = await db.items.findMany({
    skip: (page - 1) * limit,
    take: limit,
  });
  
  return Response.json({
    items,
    pagination: { page, limit },
  });
}

// ❌ BAD: Return all items
export async function GET(request: Request) {
  const items = await db.items.findMany(); // Could be millions!
  return Response.json(items);
}

// Always limit response size

4. Handle Errors Gracefully

TYPESCRIPT
// ✅ GOOD: Try-catch with proper errors
export async function GET(request: Request) {
  try {
    const data = await db.items.findMany();
    return Response.json(data);
  } catch (error) {
    console.error('Database error:', error);
    return Response.json(
      { error: 'Failed to fetch items' },
      { status: 500 }
    );
  }
}

// Don't expose internal error details to clients

5. Sanitize and Escape Input

TYPESCRIPT
// ✅ GOOD: Sanitize input
export async function POST(request: Request) {
  const body = await request.json();
  
  // Trim whitespace
  const title = body.title?.trim();
  
  // Escape HTML to prevent XSS
  const sanitizedContent = escapeHtml(body.content);
  
  // Validate after sanitization
  const result = schema.safeParse({
    title,
    content: sanitizedContent,
  });
  
  // Use validated data
}

// Protect against injection attacks

Key Takeaways

  • GET with searchParams - filter and paginate data
  • POST with request.json() - parse request body
  • 201 for POST - return created resource
  • Validate with Zod - type-safe validation
  • Always paginate - limit response sizes
  • Proper status codes - 200, 201, 400, 404, 500
  • Error handling - try-catch and graceful failures
  • FormData for files - multipart uploads

What's Next?

You've mastered GET and POST handlers! Next, we'll explore Dynamic API Routes—creating RESTful endpoints with route parameters, building resource-specific handlers like /api/posts/[id], and implementing complete CRUD operations with dynamic segments. You'll build flexible, scalable APIs!

We'll cover dynamic route parameters, RESTful patterns, single resource endpoints, and complete CRUD implementations.

📊 Performance Matters

Always paginate large datasets, add database indexes for frequently queried fields, cache responses when appropriate, and use SELECT to fetch only needed fields. Fast APIs provide better user experience!

Test Your Understanding

Question 1 of 4

How do you access query parameters in a GET request?

Master GET and POST handlers in Next.js! Learn query parameters, validation, and building RESTful APIs.

Previous
Route Handlers Introduction
Next
Dynamic API Routes

Master Next.js APIs

Join 2,000+ developers building powerful APIs with Next.js. Get the next lesson on dynamic API routes - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo