Building A Simple CRUD API With Next.Js 13
Tags: webdev, javascript, nextjs, react
Published: 2023-10-04 08:34:33

Now I will make an example about CRUD ( create, read, update, delete ) in NextJS 13 . Share with everyone how to set up routes in NextJS 13, so we can configure paths to create, read, and edit in the application. Here I use the latest version of NextJS 13. For me, I already have a BackEnd, so in this article I will only do the frontend

![Building A Simple CRUD API With Next.Js 13 -  hoanguyenit.com](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0gaiwdder5kd7r4tjqam.jpeg)
+ **app/libs/index.ts** : build the libraries you want
+ **app/types/index.ts** : build the interfaces
+ **api /posts/route.ts** : GET (Get a list of all posts), POST (Add a post)
+ **api/posts/[id]/route.ts** : 
    GET : get post via ID 
    PUT : update post from ID
    DELETE : delete post from ID
+ **app/post/page.tsx** : Display list of posts
+ **app/post/create/page.tsx** : Form to add posts 
+ **app/post/edit/[id]/page.tsx** : Form to edit posts from ID
+ **app/post/read/[id]/page.tsx** : Form to display posts from ID
+ **app/components/Header.ts** : design header interface 
+ **app/components/Post.ts** : display post data
+ **app/layout.tsx** : project layout interface 
+ **app/page.tsx** : home page interface

**Demo:**
{% embed https://www.youtube.com/watch?v=tlS9HYwewes %}

Github : [Building A Simple CRUD API With Next.Js 13](https://github.com/skipperhoa/crud-in-nextjs13)
Okay let's start building a project 

```javascript
npx create-next-app@latest
```
If you have not seen the article on creating a NextJS project, please review this article:  **[Create A Project With Next.Js](https://hoanguyenit.com/create-a-project-with-nextjs.html)**
+ **app/libs/index.ts** : The code below, we handle API requests

```javascript
export const fetcher = (url: string) => fetch(url).then((res) => res.json());
```
+ **app/types/index.ts** : set the properties of a Model, using the interface in typescript , need to configure properties of a certain data type

```javascript
export  interface UserModel{
    id:number,
    name:string,
}
export  interface PostModel{
    id:number,
    title:string,
    keyword:string,
    des:string,
    slug:string,
    image:string,
    publish:number,
    content:string,
    created_at:string
    user:UserModel,
    deletePost:(id: number)=> void;
}
export interface PostAddModel{
    title:string,
    content:string
}
```
+ **api/posts/route.ts** : We need to build a route, to request Api, here we need to install 2 methods ( **GET , POST** ) 

```javascript
import { NextRequest, NextResponse } from 'next/server'

export async function GET() {
  const res = await fetch(process.env.PATH_URL_BACKEND+'/api/posts', {
    headers: {
      'Content-Type': 'application/json',
    },
  })
  const result = await res.json()
  return NextResponse.json({ result })
}
export async function POST(request: NextRequest) {
  const body = await request.json()
  const res = await fetch(process.env.PATH_URL_BACKEND+'/api/posts', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  })
  const data = await res.json();
  return NextResponse.json(data)

}
```
**process.env.PATH_URL_BACKEND** : is the path to your BackEnd address , you create an .env file and use configuration variables for the project.

+ **api/posts/[id]/route.ts** : In this route we use methods such as ( **GET , PUT , DELETE** ), as I said in the above section 
GET: used to get posts by ID 
PUT : update post from ID 
DELETE : delete post from ID 

```javascript
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request : NextRequest,{ params }: { params: { id: number } }) {
  const res = await fetch(process.env.PATH_URL_BACKEND+`/api/posts/${params.id}`, {
    next: { revalidate: 10 } ,
    headers: {
      'Content-Type': 'application/json',
    },
  })
  const result = await res.json()
  return NextResponse.json(result)
}
export async function PUT(request: NextRequest,{ params }: { params: { id: number } }) {
  const body = await request.json()
  const res = await fetch(process.env.PATH_URL_BACKEND+`/api/posts/${params.id}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  })
  const data = await res.json();
  return NextResponse.json(data)

}
export async function DELETE(request: NextRequest,{ params }: { params: { id: number } }) {
  const res = await fetch(process.env.PATH_URL_BACKEND+`/api/posts/${params.id}`, {
    next: { revalidate: 10 },
    method: 'DELETE',
    headers: {
      'Content-Type': 'application/json',
    },
  })
  const data = await res.json();
  return NextResponse.json(data)

}
 
```
You can look at the code above, I use  **next: { revalidate: 10 }** , it is used to save data memory within 10 seconds, depending on your application, configure it. 
+ **app/post/page.tsx** : Displays a list of posts for users to see 

```javascript
"use client";
import React,{useEffect, useState} from "react";
import useSWR from "swr";
import { fetcher } from "../libs";
import Post from "../components/Post";
import { PostModel } from "../types";
import Link from "next/link";

export default function Posts() {
  const [posts,setPosts] = useState<PostModel[]>([]);
  const { data, error, isLoading } = useSWR<any>(`/api/posts`, fetcher);
  useEffect(()=>{
    if(data && data.result.data)
    {
      console.log(data.result.data);
      setPosts(data.result.data);
    }
  },[data,isLoading]);
  if (error) return <div>Failed to load</div>;
  if (isLoading) return <div>Loading...</div>;
  if (!data) return null;
  let delete_Post : PostModel['deletePost']= async (id:number) => {
    const res = await fetch(`/api/posts/${id}`, {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json'
      },
    });
    const content = await res.json();
    if(content.success>0)
    {
       
      setPosts(posts?.filter((post:PostModel)=>{  return post.id !== id  }));
    }
  }
  return (
    <div className="w-full max-w-7xl m-auto">
      <table className="w-full border-collapse border border-slate-400">
        <caption className="caption-top py-5 font-bold text-green-500 text-2xl">
          List Posts - Counter :
          <span className="text-red-500 font-bold">{ posts?.length}</span>
        </caption>
    
        <thead>
          <tr className="text-center">
            <th className="border border-slate-300">ID</th>
            <th className="border border-slate-300">Title</th>
            <th className="border border-slate-300">Hide</th>
            <th className="border border-slate-300">Created at</th>
            <th className="border border-slate-300">Modify</th>
          </tr>
        </thead>
        <tbody>
           <tr>
              <td colSpan={5}>
                 <Link href={`/post/create`} className="bg-green-500 p-2 inline-block text-white">Create</Link>
              </td>
           </tr>
           {
              posts && posts.map((item : PostModel)=><Post key={item.id} {...item} deletePost = {delete_Post} />)
           }
        </tbody>
      </table>
    </div>
  );
}
```
There are many things inside the above code that I shared with everyone in the previous article such as: **[SWR](https://dev.to/skipperhoa/create-a-example-handling-data-fetching-with-swr-in-nextjs-13-1997)**
If you haven't seen it yet, please review it here: **[Create A Example Handling Data Fetching With SWR In NextJS](https://dev.to/skipperhoa/create-a-example-handling-data-fetching-with-swr-in-nextjs-13-1997)**
Look at this code, I created a function to catch the event of deleting a post 

```javascript
let delete_Post : PostModel['deletePost']= async (id:number) => {
    const res = await fetch(`/api/posts/${id}`, {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json'
      },
    });
    const content = await res.json();
    if(content.success>0)
    {
       
      setPosts(posts?.filter((post:PostModel)=>{  return post.id !== id  }));
    }
  }
----------
//chèn function đó qua component để bắt sự kiện click delete 
posts && posts.map((item : PostModel)=><Post key={item.id} {...item} deletePost = {delete_Post} />
```
+ **app/components/Post.ts** : component displays posts and handles click events to delete posts

```javascript
import React from 'react'
import { PostModel } from '../types'
import Link from 'next/link'
export default function Post(params: PostModel) {
  return (
    <tr>
            <td className='w-10 border border-slate-300 text-center'>{params.id}</td>
            <td className='border border-slate-300'>{params.title}</td>
            <td className='border border-slate-300 text-center'>{params.publish>0?'open':'hide'}</td>
            <td className='border border-slate-300 text-center'>{params.created_at}</td>
            <td className='w-52 border border-slate-300'>
              <span onClick={()=>params.deletePost(params.id)} className='bg-red-500 p-2 inline-block text-white text-sm'>Delete</span>
              <Link href={`/post/edit/${params.id}`} className='bg-yellow-500 p-2 inline-block ml-3 text-white text-sm'>Edit</Link>
              <Link href={`/post/read/${params.id}`} className='bg-yellow-500 p-2 inline-block ml-3 text-white text-sm'>View</Link>
            </td>
    </tr>
  )
}
```
Catch click event to delete post: **params.deletePost(params.id)**
+ **app/post/create/page.tsx** : Create a form to enter information to add posts, the code below uses useState to save data, in general it is the same as React. So I will skip this explanation 

```javascript
"use client"
import React, {useState } from 'react'
import { useRouter } from 'next/navigation'
export default function PostCreate() {
  const router = useRouter()
  const [title, setTitle] =useState<string>('');
  const [body, setBody] = useState<string>('');
  const addPost = async (e: any) => {
    e.preventDefault()
    if (title!="" && body!="") {
      const formData = {
          title: title,
          content: body
      }
      const add = await fetch('/api/posts', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(formData)
      });
      const content = await add.json();
      if(content.success>0)
      {
        router.push('/post');
      }
      
    }
  };
  return (
    <form className='w-full' onSubmit={addPost}>
        <span className='font-bold text-yellow-500 py-2 block underline text-2xl'>Form Add</span>
        <div className='w-full py-2'>
             <label htmlFor="" className='text-sm font-bold py-2 block'>Title</label>
             <input type='text' name='title' className='w-full border-[1px] border-gray-200 p-2 rounded-sm'  onChange={(e:any)=>setTitle(e.target.value)}/>
        </div>
        <div className='w-full py-2'>
             <label htmlFor="" className='text-sm font-bold py-2 block'>Content</label>
             <textarea name='title' className='w-full border-[1px] border-gray-200 p-2 rounded-sm' onChange={(e:any)=>setBody(e.target.value)} />
        </div>
        <div className='w-full py-2'>
          <button className="w-20 p-2 text-white border-gray-200 border-[1px] rounded-sm bg-green-400">Submit</button>
        </div>
    </form>
  )
}
```
+ **app/post/edit/[id]/page.tsx** : Edit the post, by getting the ID of the post, request to /api/posts/edit/[id]/route.ts to get the data to edit fix

```javascript
"use client"
import React, {useState,useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { fetcher } from '@/app/libs'
import useSWR from 'swr'
export default function PostEdit({params} :{params:{id:number}}) {
  const router = useRouter()
  const {data : post,isLoading, error} = useSWR(`/api/posts/${params.id}`,fetcher)
  const [title, setTitle] =useState<string>('');
  const [body, setBody] = useState<string>('');
  useEffect(()=>{
     if(post){
         setTitle(post.result.title)
         setBody(post.result.content)
     }
  },[post, isLoading])
  const updatePost = async (e: any) => {
    e.preventDefault()
    if (title!="" && body!="") {
      const formData = {
          title: title,
          content: body
      }
      const res = await fetch(`/api/posts/${params.id}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(formData)
      });
      const content = await res.json();
      if(content.success>0)
      {
        router.push('/post');
      }
      
    }
  };
  if(isLoading) return <div><span>Loading...</span></div>
  if (!post) return null;
  return (
    <form className='w-full' onSubmit={updatePost}>
        <span className='font-bold text-yellow-500 py-2 block underline text-2xl'>Form Add</span>
        <div className='w-full py-2'>
             <label htmlFor="" className='text-sm font-bold py-2 block'>Title</label>
             <input type='text' name='title' className='w-full border-[1px] border-gray-200 p-2 rounded-sm' value={title} onChange={(e:any)=>setTitle(e.target.value)}/>
        </div>
        <div className='w-full py-2'>
             <label htmlFor="" className='text-sm font-bold py-2 block'>Content</label>
             <textarea name='title' className='w-full border-[1px] border-gray-200 p-2 rounded-sm' value={body} onChange={(e:any)=>setBody(e.target.value)} />
        </div>
        <div className='w-full py-2'>
          <button className="w-20 p-2 text-white border-gray-200 border-[1px] rounded-sm bg-green-400">Submit</button>
        </div>
    </form>
  )
}
```
+ **app/post/read/[id]/page.tsx** : Similar to Edit, but in this route we only need to display information for the user to see 

```javascript
'use client'
import { fetcher } from '@/app/libs'
import useSWR from 'swr'

export default function Detail({params}: {params:{id :number}}) {
  const {data: post, isLoading, error}  = useSWR(`/api/posts/${params.id}`,fetcher)
  if(isLoading) return <div><span>Loading...</span></div>
  if (!post) return null;
  return (
    <div className='w-full'>
        <h2 className='text-center font-bold text-3xl py-3'>{post.result.title}</h2>
     
       <div className='w-full max-w-4xl m-auto border-[1px] p-3 border-gray-500 rounded-md'>
         <p dangerouslySetInnerHTML={{ __html: post.result.content}}></p>
       
       </div>
    </div>
  )
}

```
+ **app/page.tsx** : import component **/app/post/page.tsx** , to display the main screen of the home page 

```javascript
import Posts from './post/page'
export default function Home() {
  return (
        <Posts />
  )
}
```
+ **app/layout.tsx** : application layout 

```javascript
import './globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import Header from './components/Header'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Header />
        <div className='w-full max-w-7xl mt-4 m-auto'>
            {children}
        </div>
      </body>
    </html>
  )
}
```
Demo:

![Building A Simple CRUD API With Next.Js 13 - hoanguyenit.com](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7m6qndor64fljs4n41u3.jpeg)

![Building A Simple CRUD API With Next.Js 13 - hoanguyenit.com](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y8mljemp08qcrcuzhmne.jpeg)

![Building A Simple CRUD API With Next.Js 13 - hoanguyenit.com](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8aokc8oviedbug1vdnh9.jpeg)

![Building A Simple CRUD API With Next.Js 13 - hoanguyenit.com](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g7msezvg383hrp5ts2ab.jpeg)
The Article : [Building A Simple CRUD API With Next.Js 13](https://hoanguyenit.com/building-a-simple-crud-api-with-nextjs-13.html)