📝

Next.js14とmicroCMSでCRUDの実装

2023/12/22に公開

概要

Next.js14がリリースされたので、CRUDの動作確認までやってみました。
安定版になったServerActionsもお試しで使ってます。
内容はNext.js × microCMSでシンプルなTodoAppの作成です。

環境

macOS: 14.1
Next.js: 14.0.2
Node.js: 18.18.2

プロジェクトの作成

下記のコマンドを実行してプロジェクトを作成します。

npx create-next-app@latest

ServerActionsを試すため、AppRouterを使用します。

✔ What is your project named? … next_microcms_crud
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … No

今回はmicroCMSを使用します。
microCMSはAPIベースのヘッドレスCMSです。
DBの設定をせずに簡単にCRUDの実装をするため採用してます。
また、公式でNext.jsとの連携に関するドキュメントがあるのも採用理由です。
https://document.microcms.io/tutorial/next/next-top

以下のコマンドでSDKをインストールすることで、microCMSのAPIが扱いやすくなります。

npm install microcms-js-sdk

microCMSの設定

microCMSのアカウント作成などについての詳細は、公式ドキュメントを参照してください。
今回は、コンテンツAPIを以下のようなスキーマで作成しました。

{
    "title": "タスクの追加"
}

idやcreatedAtなどは自動で生成されます。

作成できたら、APIキーの設定をします。
今回はCRUDの実装なので、POST,PATCH,DELETEの権限にチェックを入れておいてください。

/src直下に/libを作成し、内部にclient.jsを作成してSDKの初期化をします。

/lib/client.ts
import { createClient } from 'microcms-js-sdk';

export const client = createClient({
  serviceDomain: 'service-domain',  // XXXX.microcms.io の XXXX 部分
  apiKey: 'api-key',
});

これでmicroCMSの準備はOKです。

ページの作成

/appの直下に/todoを作成し、内部にpage.tsxを作成します。
ついでにglobals.cssを必要な記述のみにします。

globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

データの取得

microCMSからデータを取得し、リスト表示します。
作成したコンテンツAPIに数件適当なデータを作成しておいてください。

/todo/page.tsxを編集します。
スタイルはお好みで。

/todo/page.tsx
import { client } from "@/lib/client"

type dataType = {
  contents: contentsType[]
  totalCount: number
  offset: number
  limit: number
}
type contentsType = {
  id: string
  title: string
}

export default async function todo(){  
  //microCMSからデータを取得する処理
  const data: dataType = await client.get({
    endpoint: 'XXXX', //microCMSで設定したもの
    queries: { fields: 'id,title' },
  })
  
  return(
    <>
      <div className="mt-[1rem] w-full mx-[2rem]">
        <h2 className="text-[1.6rem]">List</h2>
          {data?.contents.map((value, index) => (
            <div key={index} className="flex my-[0.5rem]">
              <li className="w-[20rem]">{value.title}</li>
            </div>
          ))}
      </div>
    </>
  )
}

npm run devを実行してhttp://localhost:3000/todoにアクセスするとリストが表示ができました。
実行結果

データの作成

リスト表示をしたページに、フォームを作成しデータを作成する処理を書きます。

/todo/page.tsx
 ~~~

 export default async function todo(){  
   const data: dataType = await client.get({
     endpoint: 'XXXX', //microCMSで設定したもの
     queries: { fields: 'id,title' },
   })
  
+  const createTodo = async (formData: FormData) => {
+    'use server'
+    const title = formData.get('title') as string
+    const sendData = `{"title":"${title}"}`
+    // microCMSへの登録処理
+    await client.create({
+      endpoint: 'XXXX', //microCMSで設定したもの
+      content: JSON.parse(sendData),
+    })
+    // 登録処理後、/todoをrevalidateする 
+    revalidatePath('/todo','page')
+  }
   return(
     <>
       <div className="mt-[1rem] w-full mx-[2rem]">
         <h2 className="text-[1.6rem]">List</h2>
           {data?.contents.map((value, index) => (
             <div key={index} className="flex my-[0.5rem]">
               <li className="w-[20rem]">{value.title}</li>
             </div>
           ))}
       </div>
+      <div className="mt-[1rem] w-full mx-[2rem]">
+        <h2 className="text-[1.6rem] mb-[0.5rem]">Create</h2>
+        <form action={createTodo}>
+          <input type="text" name="title" placeholder="入力してください" defaultValue={""} className="border-solid border-[0.1rem] mr-[0.5rem]"/>
+          <input type="submit" value="送信" className="bg-blue-500 text-white rounded-[0.8rem] px-[1rem] cursor-pointer"/>
+        </form>
+      </div>
     </>
   )
  }

これでフォームの作成ができました。
送信ボタンを押すと、formタグで設定されているactioncreateTodoが実行され、microCMSへの登録処理をします。
revalidatePathを設定しているため、送信後にリロードをしなくても最新のデータがリストに表示されます。
データの追加

データの編集

以下の流れでデータの編集をします。

処理の流れ
URLからIDの取得
↓
IDをもとにデータを取得し表示
↓
編集ボタンクリックでmicroCMSへデータの送信
↓
`/todo`へリダイレクト

まずは/todo/page.tsxに編集ボタンを追加します。

/todo/page.tsx
  import { client } from "@/lib/client"
  import { revalidatePath } from "next/cache"
+ import Link from "next/link"

 ~~~

  return(
      <>
        <div className="mt-[1rem] w-full mx-[2rem]">
          <h2 className="text-[1.6rem]">List</h2>
            {data?.contents.map((value, index) => (
              <div key={index} className="flex my-[0.5rem]">
                <li className="w-[20rem]">{value.title}</li>
+               <Link className="bg-green-500 text-white rounded-[0.8rem] px-[1rem] mr-[0.5rem] cursor-pointer" href={`/todo/${value.id}`}>
+                 編集
+               </Link>
              </div>
            ))}
        </div>
        <div className="mt-[1rem] w-full mx-[2rem]">
          <h2 className="text-[1.6rem] mb-[0.5rem]">Create</h2>
          <form action={createTodo}>
            <input type="text" name="title" placeholder="入力してください" defaultValue={""} className="border-solid border-[0.1rem] mr-[0.5rem]"/>
            <input type="submit" value="送信" className="bg-blue-500 text-white rounded-[0.8rem] px-[1rem] cursor-pointer"/>
          </form>
        </div>
      </>
    )
   }

こんな感じで編集ボタンが追加できました。
編集ボタンの追加

次に編集ページにフォームを作成します。
/todo直下に/[id]を作成し、内部にpage.tsxを作成します。

/todo/[id]/page.tsx
import { client } from "@/lib/client"
import { revalidatePath } from "next/cache"
import { redirect } from "next/navigation"

type dataType = {
  contents: contentsType[]
  totalCount: number
  offset: number
  limit: number
}
type contentsType = {
  id: string
  title: string
}
type Props = {
  id:string
}

export default async function updateTodo({params}:{params:Props}){  
  //microCMSからデータを取得する処理
  const data: dataType = await client.get({
    endpoint: 'XXXX', //microCMSで設定したもの
    queries: { 
      fields: 'id,title' ,
      filters:`id[equals]${params.id}`
    },
  })
  
  //編集処理
  const updateTodo = async (formData: FormData) => {
    'use server'
    const title = formData.get('title') as string
    const id = formData.get('id') as string
    const sendData = `{"title":"${title}"}`
    await client.update({
      endpoint: 'XXXX', //microCMSで設定したもの
      content: JSON.parse(sendData),
      contentId: id
    })
    revalidatePath('/todo/[id]',"page")
    revalidatePath(`/todo`,"page")
    redirect('/todo')
  }
  
  return(
    <>
      <div className="mx-[2rem]">
        <h2 className="text-[1.6rem] mt-[1rem]">編集</h2>
        <div className="flex mt-[0.5rem]">
          <form action={updateTodo}>
            <input type="hidden" name="id" value={data.contents[0].id} />
            <input type="text" name="title" placeholder="編集する" defaultValue={data.contents[0].title} className="border-solid border-[0.1rem] mr-[0.5rem]"/>
            <input type="submit" value="編集" className="bg-green-500 text-white rounded-[0.8rem] px-[1rem] cursor-pointer" />
          </form>
        </div>
      </div>
    </>
  )
}

http://localhost:3000/todoにアクセスし編集ボタンを押すと、ページ遷移し以下のような挙動でデータの編集ができます。
データの編集

データの削除

最後に削除機能の実装です。
/todo/page.tsxのリストに削除ボタンと処理を追加します。

/todo/page.tsx
 ~~~

+ const deleteTodo = async (formData:FormData)=>{
+     'use server'
+     const id = formData.get('id') as string    
+     await client.delete({
+       endpoint: 'XXXX', //microCMSで設定したもの
+       contentId: id
+     })
+     revalidatePath('/todo','page')
+   }
  
    return(
      <>
        <div className="mt-[1rem] w-full mx-[2rem]">
          <h2 className="text-[1.6rem]">List</h2>
            {data?.contents.map((value, index) => (
              <div key={index} className="flex my-[0.5rem]">
                <li className="w-[20rem]">{value.title}</li>
                <Link className="bg-green-500 text-white rounded-[0.8rem] px-[1rem] mr-[0.5rem] cursor-pointer" href={`/todo/${value.id}`}>
                  編集
                </Link>
+               <form action={deleteTodo}>
+                 <input type="hidden" name="id" value={value.id} />
+                 <input type="submit" value="削除" className="bg-red-500 text-white rounded-[0.8rem] px-[1rem] cursor-pointer"/>
+               </form>
              </div>
            ))}
        </div>
        <div className="mt-[1rem] w-full mx-[2rem]">
          <h2 className="text-[1.6rem] mb-[0.5rem]">Create</h2>
          <form action={createTodo}>
            <input type="text" name="title" placeholder="入力してください" defaultValue={""} className="border-solid border-[0.1rem] mr-[0.5rem]"/>
            <input type="submit" value="送信" className="bg-blue-500 text-white rounded-[0.8rem] px-[1rem] cursor-pointer"/>
          </form>
        </div>
      </>
    )

これで削除機能の実装完了、CRUD処理ができました。

データの削除

参考

https://nextjs.org/docs
https://document.microcms.io/
https://zenn.dev/nickel/articles/92ba6c7cf37ea8

Arsaga Developers Blog

Discussion