CRUD Operations for Comments: Managing Data in Next.js
Learn to implement CRUD operations for comments in Next.js using Server Actions. Master database mutations, data validation, and UI synchronization.
Previously in this course, we explored implementing comment functionality by setting up our Prisma schema and basic data fetching. Now, we'll take that foundation to the next level by implementing the "Create" and "Delete" actions to provide a fully interactive experience for your users.
At its core, CRUD (Create, Read, Update, Delete) is the fundamental pattern for any data-driven application. In Next.js, we handle these operations using Server Actions, which allow us to execute server-side code directly from our UI components.
Defining Create and Delete Server Actions
To maintain a clean codebase, we’ll define our actions in a separate file. This keeps our Server Components focused on rendering while our logic resides in a testable, server-side context.
Create a file at app/actions/comments.ts and ensure it starts with the 'use server' directive.
TYPESCRIPTCE9178">'use server'; import { prisma } from CE9178">'@/lib/prisma'; import { revalidatePath } from CE9178">'next/cache'; export async function createComment(postId: string, formData: FormData) { const content = formData.get(CE9178">'content') as string; if (!content) return { error: CE9178">'Comment cannot be empty' }; await prisma.comment.create({ data: { content, postId, }, }); revalidatePath(CE9178">`/blog/${postId}`); } export async function deleteComment(commentId: string, postId: string) { await prisma.comment.delete({ where: { id: commentId }, }); revalidatePath(CE9178">`/blog/${postId}`); }
Integrating Actions into the UI
Now that we have our logic, we need to bind these actions to our UI. For the "Create" action, we use a form; for the "Delete" action, we use a button with a formAction or a simple handler.
Here is how you update your comment list component:
TSXimport { deleteComment } from CE9178">'@/app/actions/comments'; export function CommentItem({ comment, postId }: { comment: any, postId: string }) { const handleDelete = deleteComment.bind(null, comment.id, postId); return ( <div className="p-4 border-b"> <p>{comment.content}</p> <form action={handleDelete}> <button type="submit" className="text-red-500 text-sm">Delete</button> </form> </div> ); }
Understanding Data Flow and Revalidation
When you execute a mutation (Create or Delete), the server-side state changes, but your cached UI might not know about it. We use revalidatePath to tell Next.js: "The data at this route has changed; please refresh it."
As you move toward more complex architectures, remember that inefficient data handling can impact your database; refer to our guide on Next.js Server Actions: Connection Pooling and Scalability to ensure your app stays performant under load.
Hands-on Exercise
- Add a loading state: In your
createCommentform, use theuseFormStatushook to disable the "Submit" button while the action is pending. - Implement validation: Modify the
createCommentfunction to ensure the comment content is at least 3 characters long. - Verify persistence: Open your Prisma Studio or database dashboard and verify that the comment count updates correctly after a delete action.
Common Pitfalls
- Forgetting
revalidatePath: If you don't call this, the UI won't reflect your database changes until the user performs a hard refresh. - Security: Never trust input from the client. Always validate
formDatafields on the server before passing them to Prisma. - Action Binding: When using
.bind()in a list, ensure you are passing the correct IDs. It’s easy to accidentally pass the wrong index or ID if your map logic is nested.
FAQ
Q: Do I need to use useTransition for these actions?
A: useTransition is helpful for "optimistic updates" (making the UI feel instant). We will cover that in a later lesson, but for basic CRUD, the standard form action flow is sufficient.
Q: Where should I put my Server Actions?
A: I recommend creating an app/actions directory to keep your project structure organized as your application grows.
Q: Is it safe to expose database logic in these files?
A: Yes, because the 'use server' directive ensures this code only executes on the server. The client never sees your Prisma queries.
Recap
In this lesson, we moved beyond static data to dynamic user interaction. We’ve implemented basic CRUD operations using Server Actions, ensuring our data is correctly mutated and our UI remains in sync with the database. You now have the tools to make your blog truly interactive.
Up next: We will dive into revalidatePath and caching strategies to ensure our data updates are as performant as possible.
Work with me

Next.js Full-Stack Web App Development
A fast, SEO-ready full-stack web app built with Next.js 16 — from idea to deployed product, by an engineer who ships to production.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.


