→ cards/03-entity-actions

Chapter

03 · Entity Actions (actions/*.ts)

Server authority over data: Prisma lives here; validate, authorize, and expose get*/mutations.

Mental Model

  • Single source of truth for this entity’s data.
  • Encapsulates all Prisma access; framework-agnostic.
  • Called by Server Pages (reads) and Server Actions (writes).

File Classification

Layer: Entity data authority
Runtime: Server only
Prisma: ✅ Yes (only here)
UI Logic: ❌ Never
Server Actions: ❌ (separate files)
revalidatePath: ❌ (unless intentionally exposed)

Should Do

  • Read/write via Prisma.
  • Validate inputs; enforce authorization.
  • Expose get* reads and mutation helpers for Server Actions to call.
  • Remain framework-agnostic (no JSX, no client imports).

Should Not (and where it belongs)

  • UI logic → client components.
  • Context/hooks → client or provider files.
  • revalidatePath calls → server actions or route handlers that orchestrate mutations.
  • Internal API fetch → not needed; call Prisma directly here.

Canonical Example

import { prisma } from "@/lib/prisma";
import type Project from "@/types/project";

export async function getProjects(): Promise<Project[]> {
return prisma.project.findMany({ orderBy: { createdAt: "desc" } }); // read
}

export async function getProjectById(id: string): Promise<Project | null> {
return prisma.project.findUnique({ where: { id } }); // guarded read
}

export async function createProject(data: { name: string; ownerId: string; }) {
if (!data.name.trim()) throw new Error("Name required"); // security/validation
return prisma.project.create({ data }); // write
}

Colour-Coded Miniature

import { prisma } from "@/lib/prisma"; // 🟡

export async function getProjectById(id: string) {
return prisma.project.findUnique({ where: { id } }); // 🟡
}

Quick Rules

  • All Prisma lives here; nowhere else.
  • Validate and authorize at the edge of the entity.
  • No UI, no hooks, no JSX.
  • Server Actions call these for writes; Server Pages call these for reads.