İçeriğe geç
nextjs

Next.js API Routes: Backend Endpoints Oluşturma

Tarık Tunç
Next.js API Routes: Backend Endpoints Oluşturma

Next.js API Routes: Backend Endpoints Oluşturma

Next.js, sadece bir frontend framework değildir; aynı zamanda API endpoint'leri oluşturmanıza da olanak tanır. App Router'daki Route Handlers ile RESTful API'ler, webhook'lar ve backend servisleri oluşturabilirsiniz. Bu yaklaşım, ayrı bir backend sunucusu ihtiyacını ortadan kaldırarak full-stack geliştirme deneyimi sunar.

Route Handler Temelleri

Route Handler'lar, app dizini altında route.ts dosyası olarak tanımlanır:

// app/api/hello/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  return NextResponse.json({
    message: 'Merhaba Dünya!',
    timestamp: new Date().toISOString(),
  });
}

// POST, PUT, PATCH, DELETE, HEAD, OPTIONS da desteklenir
export async function POST(request: Request) {
  const body = await request.json();

  return NextResponse.json(
    { message: 'Kayıt oluşturuldu', data: body },
    { status: 201 }
  );
}

CRUD API Örneği

Tam bir CRUD (Create, Read, Update, Delete) API oluşturalım:

// app/api/posts/route.ts
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/db';

// GET /api/posts — Tüm yazıları getir
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 skip = (page - 1) * limit;

  const [posts, total] = await Promise.all([
    prisma.post.findMany({
      skip,
      take: limit,
      orderBy: { createdAt: 'desc' },
      include: { author: { select: { name: true } } },
    }),
    prisma.post.count(),
  ]);

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

// POST /api/posts — Yeni yazı oluştur
export async function POST(request: Request) {
  try {
    const body = await request.json();
    const { title, content, authorId } = body;

    if (!title || !content || !authorId) {
      return NextResponse.json(
        { error: 'title, content ve authorId zorunludur' },
        { status: 400 }
      );
    }

    const post = await prisma.post.create({
      data: { title, content, authorId },
    });

    return NextResponse.json(post, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: 'Sunucu hatası' },
      { status: 500 }
    );
  }
}

Dinamik Route Handler

// app/api/posts/[id]/route.ts
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/db';

// GET /api/posts/:id
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const post = await prisma.post.findUnique({
    where: { id: params.id },
    include: { author: true, comments: true },
  });

  if (!post) {
    return NextResponse.json(
      { error: 'Yazı bulunamadı' },
      { status: 404 }
    );
  }

  return NextResponse.json(post);
}

// PUT /api/posts/:id
export async function PUT(
  request: Request,
  { params }: { params: { id: string } }
) {
  const body = await request.json();

  const post = await prisma.post.update({
    where: { id: params.id },
    data: body,
  });

  return NextResponse.json(post);
}

// DELETE /api/posts/:id
export async function DELETE(
  request: Request,
  { params }: { params: { id: string } }
) {
  await prisma.post.delete({
    where: { id: params.id },
  });

  return NextResponse.json({ message: 'Yazı silindi' });
}
İpucu: route.ts ve page.ts aynı klasörde bulunamaz. API endpoint'leri için app/api/ altında, sayfalar için ayrı klasörlerde çalışın.

Request ve Response İşlemleri

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

export async function POST(request: Request) {
  // FormData okuma
  const formData = await request.formData();
  const file = formData.get('file') as File;

  if (!file) {
    return NextResponse.json(
      { error: 'Dosya bulunamadı' },
      { status: 400 }
    );
  }

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

  const filename = `${Date.now()}-${file.name}`;
  const filepath = path.join(process.cwd(), 'public', 'uploads', filename);
  await writeFile(filepath, buffer);

  return NextResponse.json({
    url: `/uploads/${filename}`,
    size: file.size,
  });
}

// Headers okuma
export async function GET(request: Request) {
  const authHeader = request.headers.get('authorization');
  const userAgent = request.headers.get('user-agent');

  // Custom response headers
  return NextResponse.json(
    { data: 'protected' },
    {
      headers: {
        'Cache-Control': 'no-store',
        'X-Custom-Header': 'value',
      },
    }
  );
}

Middleware ile API Koruma

// lib/auth.ts
import { NextResponse } from 'next/server';
import { verify } from 'jsonwebtoken';

export async function authenticateAPI(request: Request) {
  const authHeader = request.headers.get('authorization');

  if (!authHeader?.startsWith('Bearer ')) {
    return { error: 'Token gerekli', status: 401 };
  }

  try {
    const token = authHeader.split(' ')[1];
    const decoded = verify(token, process.env.JWT_SECRET!);
    return { user: decoded, error: null };
  } catch {
    return { error: 'Geçersiz token', status: 401 };
  }
}

// app/api/admin/users/route.ts
import { authenticateAPI } from '@/lib/auth';

export async function GET(request: Request) {
  const auth = await authenticateAPI(request);

  if (auth.error) {
    return NextResponse.json(
      { error: auth.error },
      { status: auth.status }
    );
  }

  const users = await prisma.user.findMany();
  return NextResponse.json(users);
}
Uyarı: API route'larında hassas işlemler yapıyorsanız mutlaka authentication ve authorization kontrolü ekleyin. CORS header'larını da doğru yapılandırın.

CORS Yapılandırması

// app/api/public/route.ts
import { NextResponse } from 'next/server';

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};

export async function OPTIONS() {
  return NextResponse.json({}, { headers: corsHeaders });
}

export async function GET() {
  const data = await fetchData();

  return NextResponse.json(data, { headers: corsHeaders });
}

Streaming Response

// app/api/stream/route.ts
export async function GET() {
  const encoder = new TextEncoder();

  const stream = new ReadableStream({
    async start(controller) {
      for (let i = 0; i < 10; i++) {
        const chunk = encoder.encode(`data: Mesaj ${i + 1}\n\n`);
        controller.enqueue(chunk);
        await new Promise((r) => setTimeout(r, 1000));
      }
      controller.close();
    },
  });

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    },
  });
}
Bilgi: Streaming response'lar, Server-Sent Events (SSE) ve gerçek zamanlı veri akışı için idealdir. ChatGPT benzeri uygulamalarda AI yanıtlarını stream etmek için sıkça kullanılır.

Webhook Handler

// app/api/webhooks/stripe/route.ts
import { NextResponse } from 'next/server';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

export async function POST(request: Request) {
  const body = await request.text();
  const signature = request.headers.get('stripe-signature')!;

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
  } catch (err) {
    return NextResponse.json(
      { error: 'Webhook doğrulaması başarısız' },
      { status: 400 }
    );
  }

  switch (event.type) {
    case 'checkout.session.completed':
      await handleCheckoutComplete(event.data.object);
      break;
    case 'invoice.paid':
      await handleInvoicePaid(event.data.object);
      break;
    default:
      console.log(`İşlenmeyen event tipi: ${event.type}`);
  }

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

Sonuç

Next.js API Routes (Route Handlers), full-stack uygulama geliştirmek için güçlü bir araçtır. CRUD işlemleri, dosya yükleme, webhook'lar ve streaming gibi çeşitli backend senaryolarını tek bir proje içinde yönetebilirsiniz. Doğru hata yönetimi, authentication ve CORS yapılandırmasıyla güvenli ve ölçeklenebilir API'ler oluşturabilirsiniz.

Kaynaklar

İlgili Yazılar