Skip to content
Back to skills

Route Handlers

ASecurity

This skill should be used when the user asks to \"create an API route\", \"add an endpoint\", \"build a REST API\", \"handle POST requests\", \"create route handlers\", \"stream responses\", or needs guidance on Next.js API development in the App Router.

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 12, 2026
ai-agentsgonextjsnodegitapibackend

Works with

  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 12, 2026

npx -y skills add thedixitjain/the-mega-skill-library --skill route-handlers --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Route Handlers?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Route Handlers
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/thedixitjain-route-handlers/badge)](https://www.skillsdirectory.com/skills/thedixitjain-route-handlers)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: route-handlers
description: "This skill should be used when the user asks to \"create an API route\", \"add an endpoint\", \"build a REST API\", \"handle POST requests\", \"create route handlers\", \"stream responses\", or needs guidance on Next.js API development in the App Router."
category: backend-and-data
source_repo: davepoon/buildwithclaude
source_path: "plugins/nextjs-expert/skills/route-handlers/SKILL.md"
source_url: https://github.com/davepoon/buildwithclaude/blob/HEAD/plugins/nextjs-expert/skills/route-handlers/SKILL.md
---


# Next.js Route Handlers

## Overview

Route Handlers allow you to create API endpoints using the Web Request and Response APIs. They're defined in `route.ts` files within the `app` directory.

## Basic Structure

### File Convention

Route handlers use `route.ts` (or `route.js`):

```
app/
├── api/
│   ├── users/
│   │   └── route.ts      # /api/users
│   └── posts/
│       ├── route.ts      # /api/posts
│       └── [id]/
│           └── route.ts  # /api/posts/:id
```

### HTTP Methods

Export functions named after HTTP methods:

```tsx
// app/api/users/route.ts
import { NextResponse } from 'next/server'

export async function GET() {
  const users = await db.user.findMany()
  return NextResponse.json(users)
}

export async function POST(request: Request) {
  const body = await request.json()
  const user = await db.user.create({ data: body })
  return NextResponse.json(user, { status: 201 })
}
```

Supported methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`

## Request Handling

### Reading Request Body

```tsx
export async function POST(request: Request) {
  // JSON body
  const json = await request.json()

  // Form data
  const formData = await request.formData()
  const name = formData.get('name')

  // Text body
  const text = await request.text()

  return NextResponse.json({ received: true })
}
```

### URL Parameters

Dynamic route parameters:

```tsx
// app/api/posts/[id]/route.ts
interface RouteContext {
  params: Promise<{ id: string }>
}

export async function GET(
  request: Request,
  context: RouteContext
) {
  const { id } = await context.params
  const post = await db.post.findUnique({ where: { id } })

  if (!post) {
    return NextResponse.json(
      { error: 'Not found' },
      { status: 404 }
    )
  }

  return NextResponse.json(post)
}
```

### Query Parameters

```tsx
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const page = searchParams.get('page') ?? '1'
  const limit = searchParams.get('limit') ?? '10'

  const posts = await db.post.findMany({
    skip: (parseInt(page) - 1) * parseInt(limit),
    take: parseInt(limit),
  })

  return NextResponse.json(posts)
}
```

### Request Headers

```tsx
export async function GET(request: Request) {
  const authHeader = request.headers.get('authorization')

  if (!authHeader?.startsWith('Bearer ')) {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    )
  }

  const token = authHeader.split(' ')[1]
  // Validate token...

  return NextResponse.json({ authenticated: true })
}
```

## Response Handling

### JSON Response

```tsx
import { NextResponse } from 'next/server'

export async function GET() {
  return NextResponse.json(
    { message: 'Hello' },
    { status: 200 }
  )
}
```

### Setting Headers

```tsx
export async function GET() {
  return NextResponse.json(
    { data: 'value' },
    {
      headers: {
        'Cache-Control': 'max-age=3600',
        'X-Custom-Header': 'custom-value',
      },
    }
  )
}
```

### Setting Cookies

```tsx
import { cookies } from 'next/headers'

export async function POST(request: Request) {
  const cookieStore = await cookies()

  // Set cookie
  cookieStore.set('session', 'abc123', {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    maxAge: 60 * 60 * 24 * 7, // 1 week
  })

  return NextResponse.json({ success: true })
}
```

### Redirects

```tsx
import { redirect } from 'next/navigation'
import { NextResponse } from 'next/server'

export async function GET() {
  // Option 1: redirect function (throws)
  redirect('/login')

  // Option 2: NextResponse.redirect
  return NextResponse.redirect(new URL('/login', request.url))
}
```

## Streaming Responses

### Text Streaming

```tsx
export async function GET() {
  const encoder = new TextEncoder()
  const stream = new ReadableStream({
    async start(controller) {
      for (let i = 0; i < 10; i++) {
        controller.enqueue(encoder.encode(`data: ${i}\n\n`))
        await new Promise(resolve => setTimeout(resolve, 100))
      }
      controller.close()
    },
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  })
}
```

### AI/LLM Streaming

```tsx
export async function POST(request: Request) {
  const { prompt } = await request.json()

  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [{ role: 'user', content: prompt }],
    stream: true,
  })

  const stream = new ReadableStream({
    async start(controller) {
      for await (const chunk of response) {
        const text = chunk.choices[0]?.delta?.content || ''
        controller.enqueue(new TextEncoder().encode(text))
      }
      controller.close()
    },
  })

  return new Response(stream, {
    headers: { 'Content-Type': 'text/plain' },
  })
}
```

## CORS Configuration

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

export async function GET() {
  return NextResponse.json(
    { data: 'value' },
    {
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
    }
  )
}
```

## Caching

### Static (Default for GET)

```tsx
// Cached by default
export async function GET() {
  const data = await fetch('https://api.example.com/data')
  return NextResponse.json(await data.json())
}
```

### Opt-out of Caching

```tsx
export const dynamic = 'force-dynamic'

export async function GET() {
  // Always fresh
}

// Or use cookies/headers (auto opts out)
import { cookies } from 'next/headers'

export async function GET() {
  const cookieStore = await cookies()
  // Now dynamic
}
```

## Error Handling

```tsx
export async function GET(request: Request) {
  try {
    const data = await riskyOperation()
    return NextResponse.json(data)
  } catch (error) {
    console.error('API Error:', error)

    if (error instanceof ValidationError) {
      return NextResponse.json(
        { error: error.message },
        { status: 400 }
      )
    }

    return NextResponse.json(
      { error: 'Internal Server Error' },
      { status: 500 }
    )
  }
}
```

## Resources

For detailed patterns, see:
- `references/http-methods.md` - Complete HTTP method guide
- `references/streaming-responses.md` - Advanced streaming patterns
- `examples/crud-api.md` - Full CRUD API example

---

**Source:** [`davepoon/buildwithclaude`](https://github.com/davepoon/buildwithclaude) → `plugins/nextjs-expert/skills/route-handlers/SKILL.md`

Files in this skill

  • SKILL.md7.2 KB
  • examples/crud-api.md12.1 KB
  • references/http-methods.md7.9 KB
  • references/streaming-responses.md10.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.