Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Dynamic Api Routes
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

Dynamic API Routes

RESTful endpoints with route parameters

RESTful APIs operate on resources identified by URLs. Dynamic route parameters let you create endpoints like /api/posts/[id] that handle individual resources. Combine with HTTP methods (GET, PUT, PATCH, DELETE) to build complete CRUD APIs that follow REST principles. Master dynamic API routes and you'll build scalable, maintainable APIs!

Basic Dynamic Route Parameter

Single Resource Endpoint

app/api/posts/[id]/route.ts
// GET single post by ID
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const id = params.id;

  const post = await db.posts.findUnique({
    where: { id },
    include: {
      author: {
        select: { id: true, name: true },
      },
    },
  });

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

  return Response.json(post);
}

// GET /api/posts/123
// Returns: { id: '123', title: '...', author: {...} }

// ✅ params from second argument
// ✅ Type-safe with TypeScript
// ✅ 404 if not found
// ✅ Include related data

Accessing Route Parameters

TYPESCRIPT
// Route Handler signature
export async function GET(
  request: Request,                    // First parameter: Request
  context: { params: { id: string } }  // Second parameter: Context with params
) {
  // Access parameter
  const id = context.params.id;
  
  // Alternative: destructure directly
  const { params } = context;
  const id = params.id;
  
  // Or in one line
  const { params: { id } } = context;
}

// TypeScript types for params
interface Params {
  id: string;
  // All params are strings
}

export async function GET(
  request: Request,
  { params }: { params: Params }
) {
  const id = params.id;
}

// ✅ Always receive request first
// ✅ params in second argument
// ✅ All params are strings (convert if needed)

Complete CRUD Operations

Collection Route (List and Create)

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

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
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const page = parseInt(searchParams.get('page') || '1');
  const limit = parseInt(searchParams.get('limit') || '10');

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

  const total = await db.posts.count();

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

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

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

  const post = await db.posts.create({
    data: {
      ...result.data,
      authorId: 'user-123', // From auth in production
    },
  });

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

// GET /api/posts - List posts
// POST /api/posts - Create post

// ✅ Collection endpoints
// ✅ GET for listing
// ✅ POST for creating

Resource Route (Get, Update, Delete)

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

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

// GET - Retrieve single post
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const post = await db.posts.findUnique({
    where: { id: params.id },
    include: {
      author: { select: { id: true, name: true } },
      comments: { orderBy: { createdAt: 'desc' } },
    },
  });

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

  return Response.json(post);
}

// PUT - Full update (replace entire resource)
export async function PUT(
  request: Request,
  { params }: { params: { id: string } }
) {
  const body = await request.json();

  // Validate all required fields for full update
  if (!body.title || !body.content) {
    return Response.json(
      { error: 'Title and content required for full update' },
      { status: 400 }
    );
  }

  try {
    const post = await db.posts.update({
      where: { id: params.id },
      data: {
        title: body.title,
        content: body.content,
        published: body.published ?? false,
      },
    });

    return Response.json(post);
  } catch (error) {
    return Response.json(
      { error: 'Post not found' },
      { status: 404 }
    );
  }
}

// PATCH - Partial update (update specific fields)
export async function PATCH(
  request: Request,
  { params }: { params: { id: string } }
) {
  const body = await request.json();
  const result = updatePostSchema.safeParse(body);

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

  try {
    const post = await db.posts.update({
      where: { id: params.id },
      data: result.data,
    });

    return Response.json(post);
  } catch (error) {
    return Response.json(
      { error: 'Post not found' },
      { status: 404 }
    );
  }
}

// DELETE - Remove resource
export async function DELETE(
  request: Request,
  { params }: { params: { id: string } }
) {
  try {
    await db.posts.delete({
      where: { id: params.id },
    });

    // Option 1: 204 No Content (no body)
    return new Response(null, { status: 204 });

    // Option 2: 200 with success message
    // return Response.json({ message: 'Post deleted successfully' });
  } catch (error) {
    return Response.json(
      { error: 'Post not found' },
      { status: 404 }
    );
  }
}

// GET /api/posts/123 - Get post
// PUT /api/posts/123 - Full update
// PATCH /api/posts/123 - Partial update
// DELETE /api/posts/123 - Delete post

// ✅ Complete CRUD
// ✅ PUT for full updates
// ✅ PATCH for partial updates
// ✅ DELETE returns 204
// ✅ All operations validated

Nested Dynamic Routes

Nested Resource Example

app/api/posts/[postId]/comments/route.ts
// GET - List comments for a specific post
export async function GET(
  request: Request,
  { params }: { params: { postId: string } }
) {
  const { searchParams } = new URL(request.url);
  const page = parseInt(searchParams.get('page') || '1');
  const limit = parseInt(searchParams.get('limit') || '20');

  // Verify post exists
  const post = await db.posts.findUnique({
    where: { id: params.postId },
  });

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

  // Get comments
  const comments = await db.comments.findMany({
    where: { postId: params.postId },
    skip: (page - 1) * limit,
    take: limit,
    orderBy: { createdAt: 'desc' },
    include: {
      author: { select: { id: true, name: true } },
    },
  });

  const total = await db.comments.count({
    where: { postId: params.postId },
  });

  return Response.json({
    comments,
    pagination: { page, limit, total },
  });
}

// POST - Create comment on post
export async function POST(
  request: Request,
  { params }: { params: { postId: string } }
) {
  const body = await request.json();

  // Validate
  if (!body.content || body.content.trim().length < 1) {
    return Response.json(
      { error: 'Comment content required' },
      { status: 400 }
    );
  }

  // Verify post exists
  const post = await db.posts.findUnique({
    where: { id: params.postId },
  });

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

  // Create comment
  const comment = await db.comments.create({
    data: {
      content: body.content,
      postId: params.postId,
      authorId: 'user-123', // From auth
    },
    include: {
      author: { select: { id: true, name: true } },
    },
  });

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

// GET /api/posts/123/comments - List post's comments
// POST /api/posts/123/comments - Add comment to post

// ✅ Nested resources
// ✅ Verify parent exists
// ✅ Filter by parent ID
// ✅ RESTful nesting

Multiple Nested Params

app/api/users/[userId]/posts/[postId]/route.ts
// GET - Get specific post by specific user
export async function GET(
  request: Request,
  { params }: { params: { userId: string; postId: string } }
) {
  // Access multiple params
  const { userId, postId } = params;

  const post = await db.posts.findFirst({
    where: {
      id: postId,
      authorId: userId, // Verify post belongs to user
    },
    include: {
      author: { select: { id: true, name: true } },
    },
  });

  if (!post) {
    return Response.json(
      { error: 'Post not found or does not belong to this user' },
      { status: 404 }
    );
  }

  return Response.json(post);
}

// DELETE - Delete user's specific post
export async function DELETE(
  request: Request,
  { params }: { params: { userId: string; postId: string } }
) {
  try {
    // Delete only if belongs to user
    const deleted = await db.posts.deleteMany({
      where: {
        id: params.postId,
        authorId: params.userId,
      },
    });

    if (deleted.count === 0) {
      return Response.json(
        { error: 'Post not found or does not belong to user' },
        { status: 404 }
      );
    }

    return new Response(null, { status: 204 });
  } catch (error) {
    return Response.json(
      { error: 'Failed to delete post' },
      { status: 500 }
    );
  }
}

// GET /api/users/user-123/posts/post-456
// DELETE /api/users/user-123/posts/post-456

// ✅ Multiple route params
// ✅ Verify ownership
// ✅ Scoped to parent resource

RESTful API Patterns

RESTful Route Structure

Standard REST Patterns

  • GET /api/posts - List posts (collection)
  • POST /api/posts - Create post (collection)
  • GET /api/posts/[id] - Get single post (resource)
  • PUT /api/posts/[id] - Full update (resource)
  • PATCH /api/posts/[id] - Partial update (resource)
  • DELETE /api/posts/[id] - Delete (resource)

Nested Resources

  • GET /api/posts/[id]/comments - List post's comments
  • POST /api/posts/[id]/comments - Create comment on post
  • GET /api/users/[id]/posts - List user's posts

Resource Naming Conventions

TYPESCRIPT
// ✅ GOOD: Plural nouns for collections
/api/posts
/api/users
/api/products
/api/comments

// ✅ GOOD: Singular for single resource
/api/posts/[id]
/api/users/[userId]

// ❌ BAD: Verbs in URLs
/api/getPosts
/api/createUser
/api/deleteProduct

// ❌ BAD: Mixed singular/plural
/api/post      // Should be /api/posts
/api/user/[id] // Should be /api/users/[id]

// Use HTTP methods for actions, not URLs

Query Parameters vs Route Parameters

TYPESCRIPT
// Route parameters - Identify specific resource
GET /api/posts/123           // params.id = '123'
GET /api/users/user-456      // params.userId = 'user-456'

// Query parameters - Filter, sort, paginate collection
GET /api/posts?category=tech&page=2
GET /api/users?role=admin&status=active

// ✅ GOOD: Route params for resource identity
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const post = await db.posts.findUnique({
    where: { id: params.id }, // Route param
  });
}

// ✅ GOOD: Query params for filtering
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const category = searchParams.get('category'); // Query param
  
  const posts = await db.posts.findMany({
    where: { category },
  });
}

Advanced Dynamic Route Patterns

Slug-Based Routes

app/api/posts/by-slug/[slug]/route.ts
// GET post by slug instead of ID
export async function GET(
  request: Request,
  { params }: { params: { slug: string } }
) {
  const post = await db.posts.findUnique({
    where: { slug: params.slug },
    include: {
      author: { select: { id: true, name: true } },
    },
  });

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

  return Response.json(post);
}

// GET /api/posts/by-slug/my-awesome-post
// ✅ Human-readable URLs
// ✅ SEO-friendly
// ✅ Unique slug constraint in database

Catch-All Routes

app/api/files/[...path]/route.ts
// Catch-all route for file paths
export async function GET(
  request: Request,
  { params }: { params: { path: string[] } }
) {
  // params.path is array of segments
  const filepath = params.path.join('/');
  
  console.log('Requested path:', filepath);
  // /api/files/images/users/avatar.jpg
  // params.path = ['images', 'users', 'avatar.jpg']

  // Serve file or return info
  const file = await getFile(filepath);
  
  if (!file) {
    return Response.json(
      { error: 'File not found' },
      { status: 404 }
    );
  }

  return Response.json({
    path: filepath,
    size: file.size,
    url: file.url,
  });
}

// GET /api/files/images/users/avatar.jpg
// ✅ [...path] captures multiple segments
// ✅ params.path is array
// ✅ Flexible file routing

Optional Catch-All Routes

app/api/search/[[...query]]/route.ts
// Optional catch-all - matches with or without segments
export async function GET(
  request: Request,
  { params }: { params: { query?: string[] } }
) {
  const query = params.query;

  if (!query || query.length === 0) {
    // GET /api/search
    return Response.json({
      message: 'Provide search query',
      examples: ['/api/search/posts/nextjs', '/api/search/users/john'],
    });
  }

  const [type, term] = query;

  if (type === 'posts') {
    const posts = await db.posts.findMany({
      where: {
        OR: [
          { title: { contains: term, mode: 'insensitive' } },
          { content: { contains: term, mode: 'insensitive' } },
        ],
      },
    });
    return Response.json({ type, term, results: posts });
  }

  if (type === 'users') {
    const users = await db.users.findMany({
      where: { name: { contains: term, mode: 'insensitive' } },
    });
    return Response.json({ type, term, results: users });
  }

  return Response.json(
    { error: 'Invalid search type' },
    { status: 400 }
  );
}

// GET /api/search - Works (empty query)
// GET /api/search/posts/nextjs - Works
// GET /api/search/users/john - Works

// ✅ [[...query]] is optional
// ✅ Flexible API structure

Dynamic API Routes Structure

RESTful organization with dynamic parameters

appImportant

Select a file or folder to see details

Dynamic API Routes Best Practices

1. Validate Route Parameters

TYPESCRIPT
// ✅ GOOD: Validate param format
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const id = params.id;
  
  // Validate UUID format
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  if (!uuidRegex.test(id)) {
    return Response.json(
      { error: 'Invalid ID format' },
      { status: 400 }
    );
  }
  
  // Proceed with query
}

// Or validate with Zod
import { z } from 'zod';

const paramsSchema = z.object({
  id: z.string().uuid(),
});

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const result = paramsSchema.safeParse(params);
  if (!result.success) {
    return Response.json(
      { error: 'Invalid parameters' },
      { status: 400 }
    );
  }
  
  // Use validated result.data.id
}

2. Return 404 for Missing Resources

TYPESCRIPT
// ✅ GOOD: Explicit 404 handling
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const resource = await db.items.findUnique({
    where: { id: params.id },
  });

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

  return Response.json(resource);
}

// ❌ BAD: Return null or 200 for missing resource
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const resource = await db.items.findUnique({
    where: { id: params.id },
  });

  return Response.json(resource); // null with 200 - wrong!
}

3. Use Appropriate HTTP Methods

TYPESCRIPT
// ✅ GOOD: Semantic HTTP methods
GET /api/posts/[id]      // Retrieve
PUT /api/posts/[id]      // Full update
PATCH /api/posts/[id]    // Partial update
DELETE /api/posts/[id]   // Delete

// ❌ BAD: POST for everything
POST /api/posts/[id]?action=get
POST /api/posts/[id]?action=update
POST /api/posts/[id]?action=delete

// Use proper HTTP methods

4. Verify Resource Ownership

TYPESCRIPT
// ✅ GOOD: Check authorization
export async function DELETE(
  request: Request,
  { params }: { params: { id: string } }
) {
  const userId = 'current-user-id'; // From auth
  
  const post = await db.posts.findFirst({
    where: {
      id: params.id,
      authorId: userId, // Verify ownership
    },
  });

  if (!post) {
    return Response.json(
      { error: 'Post not found or unauthorized' },
      { status: 404 } // Don't leak existence
    );
  }

  await db.posts.delete({ where: { id: params.id } });
  return new Response(null, { status: 204 });
}

// Protect resources from unauthorized access

5. Keep Nesting Shallow

TYPESCRIPT
// ✅ GOOD: Shallow nesting (1-2 levels)
/api/posts/[postId]/comments
/api/users/[userId]/posts

// ⚠️ ACCEPTABLE: 3 levels if necessary
/api/organizations/[orgId]/teams/[teamId]/members

// ❌ BAD: Deep nesting
/api/orgs/[orgId]/depts/[deptId]/teams/[teamId]/members/[memberId]/tasks

// Keep URLs readable and maintainable

Key Takeaways

  • [id] in folder name - creates dynamic segment
  • Collection + Resource - /posts and /posts/[id]
  • GET, PUT, PATCH, DELETE - single resource methods
  • Nested resources - /posts/[id]/comments
  • Return 404 - for missing resources
  • Validate params - check format and existence
  • RESTful patterns - plural nouns, HTTP methods

What's Next?

You've mastered dynamic API routes! Next, we'll explore Request and Response Objects—working with headers, cookies, setting custom headers, handling different content types, and using NextRequest/NextResponse for advanced features. You'll gain complete control over HTTP communication!

We'll cover request headers, response headers, cookies, redirects, rewrites, and streaming responses.

🔐 Security First

Always verify resource ownership before allowing modifications, validate all route parameters, use proper authentication, don't leak resource existence in error messages, and implement rate limiting for public APIs. Security is critical for production APIs!

Test Your Understanding

Question 1 of 4

How do you access route parameters in a Route Handler?

Master dynamic API routes in Next.js! Learn RESTful patterns and build complete CRUD APIs with route parameters.

Previous
GET and POST Request Handlers
Next
Request and Response Objects

Master Next.js APIs

Join 2,000+ developers building RESTful APIs with Next.js. Get the next lesson on Request and Response objects - 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