Mastering CRUD Operations in Next.js: A Comprehensive Guide

  • Karan Patel
  • January 30, 2025
Next.JS CRUD operation

Creating robust web applications often involves handling data through basic operations known as CRUD: Create, Read, Update, and Delete. Next.js, with its powerful features and flexibility, makes it an excellent choice for building such applications. In this guide, we’ll dive into how you can implement CRUD operations in a Next.js application.


Setting Up Your Project

First, ensure you have Node.js installed. Then, create a new Next.js project:

npx create-next-app crud-nextjs
cd crud-nextjs

Creating the Backend API

For simplicity, we’ll use a JSON file as our database. Create a data directory in the root of your project and a db.json file inside it:

{
  "posts": [
    { "id": 1, "title": "First Post", "content": "This is my first post" }
  ]
}

Next, install json-server to create a REST API:

npm install json-server

Add a script to your package.json to start the JSON server:

"scripts": {
  "json-server": "json-server --watch data/db.json --port 3001"
}

Start the JSON server:

npm run json-server

Your API is now running at http://localhost:3001/posts.


Integrating API with Next.js

Create a new file lib/api.js to handle API requests:

const API_URL = 'http://localhost:3001/posts';
export const fetchPosts = async () => {
  const res = await fetch(API_URL);
  return res.json();
};
export const createPost = async (post) => {
  const res = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(post)
  });
  return res.json();
};
export const updatePost = async (id, post) => {
  const res = await fetch(`${API_URL}/${id}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(post)
  });
  return res.json();
};
export const deletePost = async (id) => {
  const res = await fetch(`${API_URL}/${id}`, {
    method: 'DELETE'
  });
  return res.json();
};

Creating Pages and Components

Now, let’s create our pages and components for CRUD operations.

1. List Posts

Create pages/index.js to list all posts:

import { useEffect, useState } from 'react';
import { fetchPosts } from '../lib/api';
const Home = () => {
  const [posts, setPosts] = useState([]);
  useEffect(() => {
    const getPosts = async () => {
      const postsData = await fetchPosts();
      setPosts(postsData);
    };
    getPosts();
  }, []);
  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};
export default Home;

2. Create Post

Create pages/create.js:

import { useState } from 'react';
import { createPost } from '../lib/api';
const CreatePost = () => {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const handleSubmit = async (e) => {
    e.preventDefault();
    const newPost = { title, content };
    await createPost(newPost);
    setTitle('');
    setContent('');
  };
  return (
    <div>
      <h1>Create Post</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="Title"
        />
        <textarea
          value={content}
          onChange={(e) => setContent(e.target.value)}
          placeholder="Content"
        ></textarea>
        <button type="submit">Create</button>
      </form>
    </div>
  );
};
export default CreatePost;

3. Update Post

Create pages/edit/[id].js:

import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { fetchPosts, updatePost } from '../../lib/api';
const EditPost = () => {
  const router = useRouter();
  const { id } = router.query;
  const [post, setPost] = useState({ title: '', content: '' });
  useEffect(() => {
    if (id) {
      const getPost = async () => {
        const posts = await fetchPosts();
        const selectedPost = posts.find(post => post.id === Number(id));
        setPost(selectedPost);
      };
      getPost();
    }
  }, [id]);
  const handleSubmit = async (e) => {
    e.preventDefault();
    await updatePost(id, post);
    router.push('/');
  };
  return (
    <div>
      <h1>Edit Post</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={post.title}
          onChange={(e) => setPost({ ...post, title: e.target.value })}
          placeholder="Title"
        />
        <textarea
          value={post.content}
          onChange={(e) => setPost({ ...post, content: e.target.value })}
          placeholder="Content"
        ></textarea>
        <button type="submit">Update</button>
      </form>
    </div>
  );
};
export default EditPost;

4. Delete Post

Update pages/index.js to include a delete button:

import { useEffect, useState } from 'react';
import { fetchPosts, deletePost } from '../lib/api';
const Home = () => {
  const [posts, setPosts] = useState([]);
  useEffect(() => {
    const getPosts = async () => {
      const postsData = await fetchPosts();
      setPosts(postsData);
    };
    getPosts();
  }, []);
  const handleDelete = async (id) => {
    await deletePost(id);
    setPosts(posts.filter(post => post.id !== id));
  };
  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            {post.title}
            <button onClick={() => handleDelete(post.id)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
};
export default Home;

Conclusion

You’ve now implemented a basic CRUD application in Next.js. This setup provides a solid foundation for more advanced features, such as authentication, role-based access, and more complex data models. Happy coding!