CRUD (Create, Read, Update, Delete) operations are the foundation of any web application. Next.js, a powerful React framework, provides the perfect environment to build a seamless CRUD system with features such as API routes, server-side rendering (SSR), and static site generation (SSG). In this article, we will go through the process of implementing CRUD operations in a Next.js app while leveraging modern technologies like Prisma, MongoDB, and Tailwind CSS for styling.

Setting Up a Next.js Project

To begin, ensure you have Node.js installed. Then, create a new Next.js project using the following command:

npx create-next-app@latest nextjs-crud
cd nextjs-crud
npm install

Next, install Prisma and the necessary database client, such as PostgreSQL or MongoDB:

npm install @prisma/client @prisma/cli

Configuring Prisma with a Database

Initialize Prisma with:

npx prisma init

This will generate a .env file where you can configure your database connection string. For example, if you’re using PostgreSQL:

DATABASE_URL="postgresql://user:password@localhost:5432/mydatabase"

Modify prisma/schema.prisma to define a basic Post model:

model Post {
  id        String   @id @default(uuid())
  title     String
  content   String?
  createdAt DateTime @default(now())
}

Now, run the migration command:

npx prisma migrate dev --name init

This sets up your database schema and creates the Post table.

Creating API Routes

Next.js provides API routes that act as backend endpoints. Let’s create an API to handle CRUD operations inside the pages/api/posts/ directory.

Creating a Post (Create Operation)

Create a new file at pages/api/posts/index.js:

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { title, content } = req.body;
    const post = await prisma.post.create({
      data: { title, content },
    });
    res.status(201).json(post);
  }
}

Fetching Posts (Read Operation)

Modify the same API route to handle fetching posts:

if (req.method === 'GET') {
  const posts = await prisma.post.findMany();
  res.status(200).json(posts);
}

Updating a Post

Create a new file pages/api/posts/[id].js to handle updating a specific post:

export default async function handler(req, res) {
  const { id } = req.query;

  if (req.method === 'PUT') {
    const { title, content } = req.body;
    const updatedPost = await prisma.post.update({
      where: { id },
      data: { title, content },
    });
    res.status(200).json(updatedPost);
  }
}

Deleting a Post

Modify pages/api/posts/[id].js to include delete functionality:

if (req.method === 'DELETE') {
  await prisma.post.delete({ where: { id } });
  res.status(204).end();
}

Building the Frontend UI

Now, let’s create a simple UI to interact with our CRUD API.

Fetching and Displaying Posts

Modify pages/index.js:

import { useState, useEffect } from 'react';

export default function Home() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetch('/api/posts')
      .then((res) => res.json())
      .then((data) => setPosts(data));
  }, []);

  return (
    <div>
      <h1>Blog Posts</h1>
      {posts.map((post) => (
        <div key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </div>
      ))}
    </div>
  );
}

Creating a Post

Modify pages/index.js to include a form:

const handleSubmit = async (e) => {
  e.preventDefault();
  const res = await fetch('/api/posts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, content }),
  });
  if (res.ok) {
    alert('Post created!');
  }
};

Updating a Post

Add an edit button that fetches the current post details and allows modification:

const handleUpdate = async (id) => {
  await fetch(`/api/posts/${id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title: updatedTitle, content: updatedContent }),
  });
  alert('Post updated!');
};

Deleting a Post

const handleDelete = async (id) => {
  await fetch(`/api/posts/${id}`, { method: 'DELETE' });
  alert('Post deleted!');
};

Conclusion

Perfecting CRUD functionality in Next.js requires a well-structured approach that balances backend efficiency with frontend usability. By leveraging Prisma for database management, Next.js API routes for seamless server-side logic, and React hooks for dynamic user interactions, we can create a robust and scalable application.

This guide provided a comprehensive walkthrough of setting up a Next.js project, configuring Prisma, and implementing each CRUD operation with API routes. We also explored how to build a simple frontend to interact with our data, ensuring a smooth user experience.

With this foundation, you can extend your application further by adding authentication, authorization, validation, and even additional features like image uploads or real-time updates using WebSockets. The possibilities are vast, and mastering CRUD operations in Next.js will empower you to build high-performance web applications with ease. Keep experimenting, refining your approach, and optimizing performance to create a polished and production-ready Next.js app.