CoachnestCoachnest
Sign InGet Started
Back to course

Next.js 16: The Complete Developer Guide

…
—
Contents
1

What's New in Next.js 16

Reading14mFree
2

Installation, CLI & Your First Project

Reading12mFree
3

Project Structure & Conventions Deep Dive

Reading16m
4

Turbopack — The New Default Bundler

Video18m
5

Configuring TypeScript, ESLint & next.config.ts

Reading14m
6

Chapter 1 — Quiz

Quiz10m
7

App Router Fundamentals

Reading16m
8

Dynamic Routes, Catch-Alls & Type-Safe Params

Reading14m
9

Route Groups & Parallel Routes

Reading16m
10

Intercepting Routes & Modal Patterns

Reading12m
11

Loading, Error & Not-Found UI

Reading12m
12

Chapter 2 — Routing Quiz

Quiz12m
13

Understanding the Server/Client Boundary

Reading18m
14

Choosing When to use "use client"

Reading14m
15

Composing Server & Client Components

Reading14m
16

server-only, client-only & Code Splitting

Reading12m
17

Chapter 3 — Quiz

Quiz10m
18

Fetching Data in Server Components

Reading14m
19

The Next.js 16 Cache Model

Reading16m
20

Revalidation: revalidateTag, revalidatePath & On-Demand

Reading12m
21

Cache Components — Building Reusable Cached Functions

Reading14m
22

Search Params, Cookies & Dynamic APIs

Reading12m
23

Chapter 4 — Quiz

Quiz12m
24

Server Actions From First Principles

Reading16m
25

Forms with useActionState & Progressive Enhancement

Reading14m
26

Optimistic UI with useOptimistic

Reading12m
27

Validation with Zod + Server Actions

Reading12m
28

Chapter 5 — Quiz

Quiz10m
29

Static vs Dynamic vs Streaming

Reading14m
30

generateStaticParams & Pre-Rendering Dynamic Routes

Reading12m
31

Incremental Static Regeneration (ISR)

Reading12m
32

Partial Prerendering (PPR)

Reading16m
33

Edge Runtime vs Node.js Runtime

Reading12m
34

Rendering Strategies Deep Dive

Video22m
35

Chapter 6 — Quiz

Quiz12m
36

Tailwind CSS v4 with Next.js 16

Reading14m
37

CSS Modules, Global Styles & Scoped CSS

Reading10m
38

next/image — Smart, Fast Images

Reading14m
39

Fonts, Icons & Metadata

Reading12m
40

Chapter 7 — Quiz

Quiz10m
41

Middleware Fundamentals

Reading14m
42

Sessions, JWTs & Cookies

Reading16m
43

Protecting Server Components & Server Actions

Reading12m
44

Chapter 8 — Quiz

Quiz10m
45

Prisma with Next.js — The Production Setup

Reading14m

Mutations: Server Actions + Database Writes

Reading12m
47

Route Handlers & REST APIs

Reading12m
48

Chapter 9 — Quiz

Quiz10m
49

Unit & Component Testing with Vitest

Reading12m
50

End-to-End Testing with Playwright

Reading14m
51

Deploying to Vercel

Reading12m
52

Self-Hosting with Docker

Reading14m
53

Production Performance Checklist

Reading12m
54

Final Assessment — Next.js 16 Mastery

Quiz20m
←→navigate lessons
Chapter 9 of 10·Chapter 9 — Database Integration & APIs
Lesson 46 of 54Reading12 min

Mutations: Server Actions + Database Writes

#Mutations: Server Actions + Database Writes¶

The Next.js 16 mutation pattern is the same five steps, every time:

  1. 1Validate input
  2. 2Authenticate / authorize
  3. 3Mutate the database (preferably in a transaction)
  4. 4Invalidate caches
  5. 5Return a typed result (or redirect)

A Canonical Action¶

ts
47 lines
1// app/courses/actions.ts
2"use server";
3import { z } from "zod";
4import { redirect } from "next/navigation";
5import { revalidateTag, revalidatePath } from "next/cache";
6import { db } from "@/lib/db";
7import { requireUser } from "@/lib/auth-guard";
8
9const schema = z.object({
10  title: z.string().min(3).max(200),
11  description: z.string().min(20).max(5000),
12});
13
14type Result =
15  | { ok: true; slug: string }
16  | { ok: false; errors: Record<string, string> };
17
18export async function createCourse(prev: unknown, formData: FormData): Promise<Result> {
19  // 1. validate
20  const parsed = schema.safeParse(Object.fromEntries(formData));
21  if (!parsed.success) {
22    return {
23      ok: false,
24      errors: Object.fromEntries(parsed.error.issues.map((i) => [i.path[0] as string, i.message])),
25    };
26  }
27
28  // 2. auth
29  const user = await requireUser();
30
31  // 3. mutate
32  const slug = slugify(parsed.data.title);
33  const course = await db.course.create({
34    data: {
35      ...parsed.data,
36      slug,
37      createdById: user.id,
38    },
39  });
40
41  // 4. invalidate
42  revalidateTag("course");
43  revalidatePath("/dashboard");
44
45  // 5. return / redirect
46  redirect(`/courses/${course.slug}/edit`);
47}

Transactions¶

When two writes must succeed together:

ts
5 lines
1await db.$transaction(async (tx) => {
2  const order = await tx.order.create({ data: { userId, total } });
3  await tx.orderItem.createMany({ data: items.map((i) => ({ orderId: order.id, ...i })) });
4  await tx.user.update({ where: { id: userId }, data: { spent: { increment: total } } });
5});

If anything throws, the entire transaction rolls back.

Idempotency for Critical Mutations¶

For mutations that should not double-charge or double-book:

ts
5 lines
1export async function purchase(courseId: string, idempotencyKey: string) {
2  const existing = await db.payment.findUnique({ where: { idempotencyKey } });
3  if (existing) return existing;
4  // … create payment with this key
5}

Pass a UUID from the client and store it as a unique column.

Soft Deletes¶

If a row may be needed for audit / restoration, prefer:

prisma
4 lines
1model Course {
2  deletedAt DateTime?
3  // …
4}
ts
1 line
1await db.course.update({ where: { id }, data: { deletedAt: new Date() } });

Wrap reads in a helper that filters deletedAt: null by default.

Bulk Operations¶

ts
4 lines
1await db.enrollment.createMany({
2  data: students.map((s) => ({ userId: s.id, courseId })),
3  skipDuplicates: true,
4});

Far faster than N round trips. Skip duplicates avoids errors on @@unique constraints.

Pagination¶

Cursor-based — scales to any size:

ts
9 lines
1const PAGE = 20;
2const courses = await db.course.findMany({
3  take: PAGE + 1,                     // fetch one extra to know if there's another page
4  cursor: cursor ? { id: cursor } : undefined,
5  orderBy: { createdAt: "desc" },
6});
7const hasMore = courses.length > PAGE;
8const items = hasMore ? courses.slice(0, PAGE) : courses;
9const nextCursor = hasMore ? items[items.length - 1].id : null;

Previous

Prisma with Next.js — The Production Setup

Next

Route Handlers & REST APIs

Use ← → arrow keys to navigate between lessons