All topics
Frontend · Learning hub

Nextjs-api notes for developers

Master Nextjs-api with a curated set of 1 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Frontend notes
Nextjs-api

Next.js API Routes & Server

Next.js API Routes & Server Route Handlers (App Router) // app/api/users/route.ts import { NextRequest, NextResponse } from 'next/server'; export async function

Next.js API Routes & Server

Route Handlers (App Router)

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
  const { searchParams } = req.nextUrl;
  const page = Number(searchParams.get('page') ?? 1);

  const users = await db.user.findMany({
    skip: (page - 1) * 20,
    take: 20,
  });
  return NextResponse.json({ users });
}

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

// app/api/users/[id]/route.ts
export async function GET(
  req: NextRequest,
  { params }: { params: { id: string } }
) {
  const user = await db.user.findUniqueOrThrow({ where: { id: params.id } });
  return NextResponse.json(user);
}

export async function PATCH(
  req: NextRequest,
  { params }: { params: { id: string } }
) {
  const body = await req.json();
  const user = await db.user.update({ where: { id: params.id }, data: body });
  return NextResponse.json(user);
}

export async function DELETE(
  _req: NextRequest,
  { params }: { params: { id: string } }
) {
  await db.user.delete({ where: { id: params.id } });
  return new NextResponse(null, { status: 204 });
}

Middleware

// middleware.ts (root of project)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('token')?.value;
  const { pathname } = request.nextUrl;

  // Redirect unauthenticated users
  const protectedRoutes = ['/dashboard', '/settings', '/api/me'];
  if (protectedRoutes.some(r => pathname.startsWith(r)) && !token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // Add headers to all responses
  const response = NextResponse.next();
  response.headers.set('X-Content-Type-Options', 'nosniff');
  return response;
}

// Only run on these paths
export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Server Actions

// app/actions/posts.ts
'use server';

import { revalidatePath, revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';
import { auth } from '@clerk/nextjs/server';
import { z } from 'zod';

const PostSchema = z.object({
  title: z.string().min(1).max(200),
  body: z.string().min(1),
});

export async function createPost(formData: FormData) {
  const { userId } = auth();
  if (!userId) throw new Error('Unauthorized');

  const result = PostSchema.safeParse({
    title: formData.get('title'),
    body: formData.get('body'),
  });
  if (!result.success) return { error: result.error.flatten() };

  const post = await db.post.create({
    data: { ...result.data, authorId: userId },
  });

  revalidatePath('/posts');         // invalidate cached page
  revalidateTag('posts');           // invalidate by cache tag
  redirect(`/posts/${post.id}`);  // redirect after mutation
}

// Use in a Server Component form
// <form action={createPost}>
//   <input name="title" />
//   <textarea name="body" />
//   <button type="submit">Create</button>
// </form>

// Or call from client component
'use client';
import { createPost } from '@/app/actions/posts';
const [state, formAction] = useFormState(createPost, null);

Caching & Rendering

// fetch cache options
fetch('https://api.example.com/data', {
  cache: 'force-cache',           // SSG — cached indefinitely
  next: { revalidate: 3600 },     // ISR — revalidate every hour
});
fetch('https://api.example.com/data', {
  cache: 'no-store',              // SSR — never cached
});

// unstable_cache for DB queries
import { unstable_cache } from 'next/cache';

const getUser = unstable_cache(
  async (id: string) => db.user.findUnique({ where: { id } }),
  ['user'],
  { revalidate: 300, tags: ['user'] }
);

// generateStaticParams — SSG for dynamic routes
export async function generateStaticParams() {
  const posts = await db.post.findMany({ select: { slug: true } });
  return posts.map(p => ({ slug: p.slug }));
}

// Streaming with Suspense
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { UserStats, Skeleton } from '@/components';

export default function DashboardPage() {
  return (
    <main>
      <h1>Dashboard</h1>
      <Suspense fallback={<Skeleton />}>
        <UserStats />  {/* async Server Component, streamed */}
      </Suspense>
    </main>
  );
}

Keep your Nextjs-api knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever