Overview

Next.js API routes allow you to create API endpoints within your application. They provide a powerful way to build RESTful APIs, handle webhooks, and integrate with external services.

API Route Benefits

  • Built-in HTTP handling - Automatic request/response parsing - Middleware support - TypeScript support

Use Cases

  • RESTful APIs - Webhook endpoints - External API integrations - File uploads

Basic API Routes

Simple GET Route

// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/database";

export async function GET() {
  try {
    const users = await db.users.findMany({
      select: {
        id: true,
        name: true,
        email: true,
        createdAt: true,
      },
    });

    return NextResponse.json({ success: true, data: users });
  } catch (error) {
    console.error("Error fetching users:", error);
    return NextResponse.json(
      { success: false, error: "Failed to fetch users" },
      { status: 500 }
    );
  }
}

POST Route with Validation

// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/database";
import { z } from "zod";

const userSchema = z.object({
  name: z.string().min(1, "Name is required"),
  email: z.string().email("Invalid email address"),
  role: z.enum(["admin", "user", "viewer"]),
});

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();

    // Validate input
    const validationResult = userSchema.safeParse(body);

    if (!validationResult.success) {
      return NextResponse.json(
        {
          success: false,
          error: "Validation failed",
          details: validationResult.error.errors,
        },
        { status: 400 }
      );
    }

    // Check for existing user
    const existingUser = await db.users.findUnique({
      where: { email: body.email },
    });

    if (existingUser) {
      return NextResponse.json(
        { success: false, error: "User with this email already exists" },
        { status: 409 }
      );
    }

    const user = await db.users.create({
      data: validationResult.data,
    });

    return NextResponse.json({ success: true, data: user }, { status: 201 });
  } catch (error) {
    console.error("Error creating user:", error);
    return NextResponse.json(
      { success: false, error: "Failed to create user" },
      { status: 500 }
    );
  }
}

Dynamic Routes

Single Dynamic Parameter

// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/database";

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const user = await db.users.findUnique({
      where: { id: params.id },
      select: {
        id: true,
        name: true,
        email: true,
        role: true,
        createdAt: true,
        updatedAt: true,
      },
    });

    if (!user) {
      return NextResponse.json(
        { success: false, error: "User not found" },
        { status: 404 }
      );
    }

    return NextResponse.json({ success: true, data: user });
  } catch (error) {
    console.error("Error fetching user:", error);
    return NextResponse.json(
      { success: false, error: "Failed to fetch user" },
      { status: 500 }
    );
  }
}

export async function PUT(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const body = await request.json();

    const user = await db.users.update({
      where: { id: params.id },
      data: body,
    });

    return NextResponse.json({ success: true, data: user });
  } catch (error) {
    console.error("Error updating user:", error);
    return NextResponse.json(
      { success: false, error: "Failed to update user" },
      { status: 500 }
    );
  }
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    await db.users.delete({
      where: { id: params.id },
    });

    return NextResponse.json({ success: true, message: "User deleted" });
  } catch (error) {
    console.error("Error deleting user:", error);
    return NextResponse.json(
      { success: false, error: "Failed to delete user" },
      { status: 500 }
    );
  }
}

Multiple Dynamic Parameters

// app/api/posts/[userId]/[postId]/route.ts
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/database";

export async function GET(
  request: NextRequest,
  { params }: { params: { userId: string; postId: string } }
) {
  try {
    const post = await db.posts.findFirst({
      where: {
        id: params.postId,
        authorId: params.userId,
      },
      include: {
        author: {
          select: {
            id: true,
            name: true,
            email: true,
          },
        },
      },
    });

    if (!post) {
      return NextResponse.json(
        { success: false, error: "Post not found" },
        { status: 404 }
      );
    }

    return NextResponse.json({ success: true, data: post });
  } catch (error) {
    console.error("Error fetching post:", error);
    return NextResponse.json(
      { success: false, error: "Failed to fetch post" },
      { status: 500 }
    );
  }
}

Advanced API Patterns

Authentication Middleware

// app/api/middleware/auth.ts
import { NextRequest, NextResponse } from "next/server";
import jwt from "jsonwebtoken";

export function withAuth(handler: Function) {
  return async (request: NextRequest, context: any) => {
    try {
      const token = request.headers
        .get("authorization")
        ?.replace("Bearer ", "");

      if (!token) {
        return NextResponse.json(
          { success: false, error: "No token provided" },
          { status: 401 }
        );
      }

      const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any;
      request.user = decoded;

      return handler(request, context);
    } catch (error) {
      return NextResponse.json(
        { success: false, error: "Invalid token" },
        { status: 401 }
      );
    }
  };
}

Using Authentication Middleware

// app/api/protected/route.ts
import { NextRequest, NextResponse } from "next/server";
import { withAuth } from "../middleware/auth";

async function handler(request: NextRequest) {
  return NextResponse.json({
    success: true,
    message: "Protected route accessed",
    user: request.user,
  });
}

export const GET = withAuth(handler);

Rate Limiting

// app/api/middleware/rate-limit.ts
import { NextRequest, NextResponse } from "next/server";

const rateLimitMap = new Map();

export function withRateLimit(
  handler: Function,
  limit: number = 100,
  windowMs: number = 60000
) {
  return async (request: NextRequest, context: any) => {
    const ip =
      request.ip || request.headers.get("x-forwarded-for") || "unknown";
    const now = Date.now();
    const windowStart = now - windowMs;

    // Clean old entries
    for (const [key, value] of rateLimitMap.entries()) {
      if (value.timestamp < windowStart) {
        rateLimitMap.delete(key);
      }
    }

    // Check rate limit
    const userLimit = rateLimitMap.get(ip);

    if (userLimit) {
      if (userLimit.count >= limit) {
        return NextResponse.json(
          { success: false, error: "Rate limit exceeded" },
          { status: 429 }
        );
      }
      userLimit.count++;
    } else {
      rateLimitMap.set(ip, { count: 1, timestamp: now });
    }

    return handler(request, context);
  };
}

File Upload Handling

Single File Upload

// app/api/upload/route.ts
import { NextRequest, NextResponse } from "next/server";
import { writeFile } from "fs/promises";
import { join } from "path";

export async function POST(request: NextRequest) {
  try {
    const formData = await request.formData();
    const file = formData.get("file") as File;

    if (!file) {
      return NextResponse.json(
        { success: false, error: "No file provided" },
        { status: 400 }
      );
    }

    // Validate file type
    const allowedTypes = ["image/jpeg", "image/png", "image/gif"];
    if (!allowedTypes.includes(file.type)) {
      return NextResponse.json(
        { success: false, error: "Invalid file type" },
        { status: 400 }
      );
    }

    // Validate file size (5MB limit)
    if (file.size > 5 * 1024 * 1024) {
      return NextResponse.json(
        { success: false, error: "File too large" },
        { status: 400 }
      );
    }

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

    const filename = `${Date.now()}-${file.name}`;
    const path = join(process.cwd(), "public/uploads", filename);

    await writeFile(path, buffer);

    return NextResponse.json({
      success: true,
      filename,
      url: `/uploads/${filename}`,
    });
  } catch (error) {
    console.error("File upload error:", error);
    return NextResponse.json(
      { success: false, error: "Failed to upload file" },
      { status: 500 }
    );
  }
}

Multiple File Upload

// app/api/upload/multiple/route.ts
import { NextRequest, NextResponse } from "next/server";
import { writeFile } from "fs/promises";
import { join } from "path";

export async function POST(request: NextRequest) {
  try {
    const formData = await request.formData();
    const files = formData.getAll("files") as File[];

    if (!files || files.length === 0) {
      return NextResponse.json(
        { success: false, error: "No files provided" },
        { status: 400 }
      );
    }

    const uploadResults = [];

    for (const file of files) {
      // Validate file type
      const allowedTypes = ["image/jpeg", "image/png", "image/gif"];
      if (!allowedTypes.includes(file.type)) {
        uploadResults.push({
          filename: file.name,
          success: false,
          error: "Invalid file type",
        });
        continue;
      }

      // Validate file size (5MB limit)
      if (file.size > 5 * 1024 * 1024) {
        uploadResults.push({
          filename: file.name,
          success: false,
          error: "File too large",
        });
        continue;
      }

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

        const filename = `${Date.now()}-${file.name}`;
        const path = join(process.cwd(), "public/uploads", filename);

        await writeFile(path, buffer);

        uploadResults.push({
          filename: file.name,
          success: true,
          url: `/uploads/${filename}`,
        });
      } catch (error) {
        uploadResults.push({
          filename: file.name,
          success: false,
          error: "Upload failed",
        });
      }
    }

    return NextResponse.json({
      success: true,
      results: uploadResults,
    });
  } catch (error) {
    console.error("Multiple file upload error:", error);
    return NextResponse.json(
      { success: false, error: "Failed to upload files" },
      { status: 500 }
    );
  }
}

Webhook Handling

Stripe Webhook

// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
import { db } from "@/lib/database";

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

export async function POST(request: NextRequest) {
  try {
    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 (error) {
      console.error("Webhook signature verification failed:", error);
      return NextResponse.json(
        { success: false, error: "Invalid signature" },
        { status: 400 }
      );
    }

    // Handle the event
    switch (event.type) {
      case "payment_intent.succeeded":
        const paymentIntent = event.data.object as Stripe.PaymentIntent;
        await handlePaymentSuccess(paymentIntent);
        break;

      case "payment_intent.payment_failed":
        const failedPayment = event.data.object as Stripe.PaymentIntent;
        await handlePaymentFailure(failedPayment);
        break;

      default:
        console.log(`Unhandled event type: ${event.type}`);
    }

    return NextResponse.json({ success: true });
  } catch (error) {
    console.error("Webhook error:", error);
    return NextResponse.json(
      { success: false, error: "Webhook processing failed" },
      { status: 500 }
    );
  }
}

async function handlePaymentSuccess(paymentIntent: Stripe.PaymentIntent) {
  // Update order status in database
  await db.orders.update({
    where: { paymentIntentId: paymentIntent.id },
    data: { status: "paid" },
  });
}

async function handlePaymentFailure(paymentIntent: Stripe.PaymentIntent) {
  // Update order status in database
  await db.orders.update({
    where: { paymentIntentId: paymentIntent.id },
    data: { status: "failed" },
  });
}

Error Handling and Logging

Comprehensive Error Handling

// app/api/error-handling/route.ts
import { NextRequest, NextResponse } from "next/server";
import { db } from "@/lib/database";

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const page = parseInt(searchParams.get("page") || "1");
    const limit = parseInt(searchParams.get("limit") || "10");

    // Validate parameters
    if (page < 1 || limit < 1 || limit > 100) {
      return NextResponse.json(
        {
          success: false,
          error: "Invalid parameters",
          details: "Page must be >= 1, limit must be between 1 and 100",
        },
        { status: 400 }
      );
    }

    const skip = (page - 1) * limit;

    const [items, total] = await Promise.all([
      db.items.findMany({
        skip,
        take: limit,
        orderBy: { createdAt: "desc" },
      }),
      db.items.count(),
    ]);

    return NextResponse.json({
      success: true,
      data: items,
      pagination: {
        page,
        limit,
        total,
        pages: Math.ceil(total / limit),
      },
    });
  } catch (error) {
    console.error("API Error:", error);

    // Log error details for debugging
    if (process.env.NODE_ENV === "development") {
      console.error("Error details:", error);
    }

    return NextResponse.json(
      { success: false, error: "Internal server error" },
      { status: 500 }
    );
  }
}

Best Practices

API Route Best Practices

1

Input Validation

Always validate and sanitize input data
2

Error Handling

Provide comprehensive error handling and logging
3

HTTP Status Codes

Use appropriate HTTP status codes for responses
4

Security

Implement authentication, authorization, and rate limiting

Performance Optimization

Database Optimization

  • Use proper indexing - Implement connection pooling - Use database transactions - Optimize queries

Response Optimization

  • Implement caching - Use compression - Optimize JSON responses - Implement pagination

Common Patterns

CRUD API

// app/api/items/route.ts
export async function GET() {
  // Read all items
}

export async function POST(request: NextRequest) {
  // Create new item
}

// app/api/items/[id]/route.ts
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  // Read single item
}

export async function PUT(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  // Update item
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  // Delete item
}

Search API

// app/api/search/route.ts
export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const query = searchParams.get("q");
  const category = searchParams.get("category");
  const page = parseInt(searchParams.get("page") || "1");

  // Implement search logic
}

Troubleshooting

Key Takeaway: API routes provide a powerful way to build RESTful APIs within your Next.js application. Use them for data operations, webhooks, and external integrations while following best practices for security, validation, and error handling.

Overview

Server Actions are a Next.js feature that allows you to run server-side code directly from client components. They provide a secure, type-safe way to handle form submissions and other server operations.

Server Action Benefits

  • Type-safe server operations - Automatic serialization - Built-in error handling - Form integration

Use Cases

  • Form submissions - Database mutations - File uploads - API integrations

Basic Server Actions

Simple Server Action

// app/actions/user-actions.ts
"use server";

import { db } from "@/lib/database";
import { revalidatePath } from "next/cache";

export async function createUser(formData: FormData) {
  try {
    const user = await db.users.create({
      data: {
        name: formData.get("name") as string,
        email: formData.get("email") as string,
        role: formData.get("role") as string,
      },
    });

    revalidatePath("/users");
    return { success: true, user };
  } catch (error) {
    return { success: false, error: "Failed to create user" };
  }
}

Using Server Actions in Forms

// app/components/UserForm.tsx
import { createUser } from "@/app/actions/user-actions";

export default function UserForm() {
  return (
    <form action={createUser} className="space-y-4">
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          required
          className="w-full p-2 border rounded"
        />
      </div>

      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          required
          className="w-full p-2 border rounded"
        />
      </div>

      <div>
        <label htmlFor="role">Role:</label>
        <select id="role" name="role" className="w-full p-2 border rounded">
          <option value="admin">Admin</option>
          <option value="user">User</option>
          <option value="viewer">Viewer</option>
        </select>
      </div>

      <button
        type="submit"
        className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600">
        Create User
      </button>
    </form>
  );
}

Advanced Server Actions

Server Action with Validation

// app/actions/user-actions.ts
"use server";

import { db } from "@/lib/database";
import { revalidatePath } from "next/cache";
import { z } from "zod";

const userSchema = z.object({
  name: z.string().min(1, "Name is required"),
  email: z.string().email("Invalid email address"),
  role: z.enum(["admin", "user", "viewer"]),
});

export async function createUser(formData: FormData) {
  try {
    const data = {
      name: formData.get("name") as string,
      email: formData.get("email") as string,
      role: formData.get("role") as string,
    };

    // Validate input
    const validationResult = userSchema.safeParse(data);

    if (!validationResult.success) {
      return {
        success: false,
        error: "Validation failed",
        details: validationResult.error.errors,
      };
    }

    // Check for existing user
    const existingUser = await db.users.findUnique({
      where: { email: data.email },
    });

    if (existingUser) {
      return {
        success: false,
        error: "User with this email already exists",
      };
    }

    const user = await db.users.create({
      data: validationResult.data,
    });

    revalidatePath("/users");
    return { success: true, user };
  } catch (error) {
    console.error("User creation error:", error);
    return { success: false, error: "Failed to create user" };
  }
}

Server Action with File Upload

// app/actions/file-actions.ts
"use server";

import { writeFile } from "fs/promises";
import { join } from "path";
import { revalidatePath } from "next/cache";

export async function uploadFile(formData: FormData) {
  try {
    const file = formData.get("file") as File;

    if (!file) {
      return { success: false, error: "No file provided" };
    }

    // Validate file type
    const allowedTypes = ["image/jpeg", "image/png", "image/gif"];
    if (!allowedTypes.includes(file.type)) {
      return { success: false, error: "Invalid file type" };
    }

    // Validate file size (5MB limit)
    if (file.size > 5 * 1024 * 1024) {
      return { success: false, error: "File too large" };
    }

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

    const filename = `${Date.now()}-${file.name}`;
    const path = join(process.cwd(), "public/uploads", filename);

    await writeFile(path, buffer);

    revalidatePath("/uploads");
    return { success: true, filename };
  } catch (error) {
    console.error("File upload error:", error);
    return { success: false, error: "Failed to upload file" };
  }
}

Form Handling Patterns

Progressive Enhancement

// app/components/ContactForm.tsx
"use client";

import { useState } from "react";
import { submitContactForm } from "@/app/actions/contact-actions";

export default function ContactForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [message, setMessage] = useState("");

  const handleSubmit = async (formData: FormData) => {
    setIsSubmitting(true);
    setMessage("");

    try {
      const result = await submitContactForm(formData);

      if (result.success) {
        setMessage("Message sent successfully!");
        // Reset form
        const form = document.getElementById("contact-form") as HTMLFormElement;
        form?.reset();
      } else {
        setMessage(result.error || "Failed to send message");
      }
    } catch (error) {
      setMessage("An error occurred. Please try again.");
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <form id="contact-form" action={handleSubmit} className="space-y-4">
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="name"
          name="name"
          required
          className="w-full p-2 border rounded"
        />
      </div>

      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          name="email"
          required
          className="w-full p-2 border rounded"
        />
      </div>

      <div>
        <label htmlFor="message">Message:</label>
        <textarea
          id="message"
          name="message"
          required
          rows={4}
          className="w-full p-2 border rounded"
        />
      </div>

      <button
        type="submit"
        disabled={isSubmitting}
        className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600 disabled:opacity-50">
        {isSubmitting ? "Sending..." : "Send Message"}
      </button>

      {message && (
        <div
          className={`p-2 rounded ${
            message.includes("success")
              ? "bg-green-100 text-green-800"
              : "bg-red-100 text-red-800"
          }`}>
          {message}
        </div>
      )}
    </form>
  );
}

Multi-Step Forms

// app/actions/multi-step-actions.ts
"use server";

import { db } from "@/lib/database";
import { revalidatePath } from "next/cache";

export async function saveStep1(formData: FormData) {
  try {
    const data = {
      name: formData.get("name") as string,
      email: formData.get("email") as string,
    };

    // Save to temporary storage or session
    // This is a simplified example
    return { success: true, data };
  } catch (error) {
    return { success: false, error: "Failed to save step 1" };
  }
}

export async function saveStep2(formData: FormData) {
  try {
    const data = {
      company: formData.get("company") as string,
      position: formData.get("position") as string,
    };

    return { success: true, data };
  } catch (error) {
    return { success: false, error: "Failed to save step 2" };
  }
}

export async function completeRegistration(formData: FormData) {
  try {
    const userData = {
      name: formData.get("name") as string,
      email: formData.get("email") as string,
      company: formData.get("company") as string,
      position: formData.get("position") as string,
    };

    const user = await db.users.create({
      data: userData,
    });

    revalidatePath("/users");
    return { success: true, user };
  } catch (error) {
    return { success: false, error: "Failed to complete registration" };
  }
}

Error Handling

Comprehensive Error Handling

// app/actions/error-handling-actions.ts
"use server";

import { db } from "@/lib/database";
import { revalidatePath } from "next/cache";

export async function createPost(formData: FormData) {
  try {
    const title = formData.get("title") as string;
    const content = formData.get("content") as string;
    const authorId = formData.get("authorId") as string;

    // Validate required fields
    if (!title || !content || !authorId) {
      return {
        success: false,
        error: "All fields are required",
        field: !title ? "title" : !content ? "content" : "authorId",
      };
    }

    // Check if author exists
    const author = await db.users.findUnique({
      where: { id: authorId },
    });

    if (!author) {
      return {
        success: false,
        error: "Author not found",
        field: "authorId",
      };
    }

    // Create post
    const post = await db.posts.create({
      data: {
        title,
        content,
        authorId,
        publishedAt: new Date(),
      },
    });

    revalidatePath("/posts");
    return { success: true, post };
  } catch (error) {
    console.error("Post creation error:", error);

    // Handle specific database errors
    if (error instanceof Error) {
      if (error.message.includes("Unique constraint")) {
        return {
          success: false,
          error: "A post with this title already exists",
          field: "title",
        };
      }
    }

    return {
      success: false,
      error: "Failed to create post. Please try again.",
    };
  }
}

Real-World Examples

E-commerce Product Management

// app/actions/product-actions.ts
"use server";

import { db } from "@/lib/database";
import { revalidatePath } from "next/cache";
import { z } from "zod";

const productSchema = z.object({
  name: z.string().min(1, "Product name is required"),
  description: z.string().min(10, "Description must be at least 10 characters"),
  price: z.number().positive("Price must be positive"),
  categoryId: z.string().min(1, "Category is required"),
  stock: z.number().int().min(0, "Stock cannot be negative"),
});

export async function createProduct(formData: FormData) {
  try {
    const data = {
      name: formData.get("name") as string,
      description: formData.get("description") as string,
      price: parseFloat(formData.get("price") as string),
      categoryId: formData.get("categoryId") as string,
      stock: parseInt(formData.get("stock") as string),
    };

    // Validate input
    const validationResult = productSchema.safeParse(data);

    if (!validationResult.success) {
      return {
        success: false,
        error: "Validation failed",
        details: validationResult.error.errors,
      };
    }

    // Check if category exists
    const category = await db.categories.findUnique({
      where: { id: data.categoryId },
    });

    if (!category) {
      return {
        success: false,
        error: "Category not found",
        field: "categoryId",
      };
    }

    // Create product
    const product = await db.products.create({
      data: validationResult.data,
      include: {
        category: true,
      },
    });

    revalidatePath("/products");
    return { success: true, product };
  } catch (error) {
    console.error("Product creation error:", error);
    return { success: false, error: "Failed to create product" };
  }
}

export async function updateProduct(id: string, formData: FormData) {
  try {
    const data = {
      name: formData.get("name") as string,
      description: formData.get("description") as string,
      price: parseFloat(formData.get("price") as string),
      categoryId: formData.get("categoryId") as string,
      stock: parseInt(formData.get("stock") as string),
    };

    const validationResult = productSchema.safeParse(data);

    if (!validationResult.success) {
      return {
        success: false,
        error: "Validation failed",
        details: validationResult.error.errors,
      };
    }

    const product = await db.products.update({
      where: { id },
      data: validationResult.data,
      include: {
        category: true,
      },
    });

    revalidatePath("/products");
    revalidatePath(`/products/${id}`);
    return { success: true, product };
  } catch (error) {
    console.error("Product update error:", error);
    return { success: false, error: "Failed to update product" };
  }
}

export async function deleteProduct(id: string) {
  try {
    await db.products.delete({
      where: { id },
    });

    revalidatePath("/products");
    return { success: true };
  } catch (error) {
    console.error("Product deletion error:", error);
    return { success: false, error: "Failed to delete product" };
  }
}

Best Practices

Server Action Best Practices

1

Input Validation

Always validate input data using schemas
2

Error Handling

Provide comprehensive error handling and user feedback
3

Revalidation

Use revalidatePath to update cached data
4

Security

Implement proper authentication and authorization

Performance Considerations

Optimization Tips

  • Use database transactions for multiple operations - Implement proper indexing - Cache frequently accessed data - Use connection pooling

Security Tips

  • Validate all inputs - Sanitize data before database operations - Implement rate limiting - Use proper authentication

Common Patterns

CRUD Operations

// app/actions/crud-actions.ts
"use server";

import { db } from "@/lib/database";
import { revalidatePath } from "next/cache";

export async function createItem(formData: FormData) {
  // Create logic
}

export async function readItem(id: string) {
  // Read logic
}

export async function updateItem(id: string, formData: FormData) {
  // Update logic
}

export async function deleteItem(id: string) {
  // Delete logic
}

Batch Operations

// app/actions/batch-actions.ts
"use server";

import { db } from "@/lib/database";
import { revalidatePath } from "next/cache";

export async function bulkUpdateItems(formData: FormData) {
  try {
    const items = JSON.parse(formData.get("items") as string);

    await db.$transaction(
      items.map((item: any) =>
        db.items.update({
          where: { id: item.id },
          data: item.data,
        })
      )
    );

    revalidatePath("/items");
    return { success: true };
  } catch (error) {
    return { success: false, error: "Failed to update items" };
  }
}

Troubleshooting

Key Takeaway: Server Actions provide a powerful way to handle server-side operations with type safety and automatic serialization. Use them for form submissions, database operations, and other server-side tasks while maintaining good error handling and validation practices.