Next.jsでPrismaとSupabaseを使ってみる API編
概要
前回はTODOリストでPrismaの設定までおこなったので、今度はNext.jsでAPIのエンドポイントを作成して、TODOの追加・削除・アップデートなどやってみる。
APIでエンドポイントを作成
appディレクトリ配下にapi/todo
というフォルダ構成にしてroute.ts
を作成する。
Next.js 13からはAPIのファイルはroute.ts
という名前で作成する必要があります。
またTODOを削除するにはIDが必要になるので、[id]
フォルダを作成して、route.ts
で削除など行う。下記がディレクトリ構成。
app/
├── api/todo
│ └── route.ts
│ └── [id]
│ └── route.ts
|── page.tsx
Prisma Clientでインスタンスを作成
まず、データベースの操作するにはprismaのインスタンスを作成する必要がある。
前回、Prisma Clientをインストールしたのでこれを使用する。
import { PrismaClient } from "@prisma/client";
//インスタンスを作成
const prisma = new PrismaClient();
// データベースに接続する関数
export const connect = async () => {
try {
//prismaでデータベースに接続
prisma.$connect();
} catch (error) {
return Error("DB接続失敗しました")
}
}
上記のように、PrismaClient
をインポートしてnew PrismaClient()
でインスタンスを作成。
接続用のconnect
関数を作っておいて、GET
やPOST
などから呼び出せるようにしておきます。
このconnect()
などは以後、説明は省略。
GETでTODO一覧を取得
TODOの一覧を取得するためのGET
関数を記載します。
とりあえずSupabaseの画面から記事をダミーで作成するなどしておく。
ちなみにGET
関数だが、Get
など小文字を利用すると実行されないので注意する。
// データベースからデータを取得する
export const GET = async (req: Request) => {
try {
await connect();
const todos = await prisma.todo.findMany();
return NextResponse.json({todos},{ status: 200 })
} catch (error) {
return NextResponse.json({ messeage: "Error" },{ status: 500 })
} finally {
//必ず実行する
await prisma.$disconnect();
}
}
TODOのデータを取得するにはfindMany()
を実行します。
またprisma.todo
のtodo
だがスキーマを設定した際のTodo
の小文字となるので、例えばスキーマの設定でPost
としたら、prisma.post
となる。
取得したデータをtodos
で受け取って、NextResponse.json
で返す。
finally
では成功してもエラーでも接続を解除するための、$disconnect()
を実行する。
また、値を返すのにres.status
としていたが、新しいNext.jsではNextResponse
を利用する。
return res.status(200).json({ message: "ユーザー登録成功" })
これでGETによるTODO一覧の取得するAPIのGETは終わり。
この後は同じようにPOSTでTODOを追加などするのだが、一旦VSCODEのPOSTMANの拡張機能を使ってAPIでGETを叩くと以下のようにJSONが取得できてるのが分かる。
POSTでTODOを追加する
今度は、TODOを追加してみる。
export const POST = async (req: Request, res: NextResponse) => {
const { title } = await req.json();
try {
await connect();
const todo = await prisma.todo.create({
data: {
title: title,
createdAt: new Date()
}
});
return NextResponse.json({ message: "投稿完了"}, { status: 200 })
} catch (error) {
return NextResponse.json({ messeage: "投稿失敗" }, { status: 500 })
} finally {
await prisma.$disconnect();
}
}
GETと違うのフォームからの値を取得する必要がある。
以前までのNext.jsでフォームの値を取得するにはres.body
だったが、新しいNext.jsでは、req.json()
で取得となるので、分割代入でTODOのタイトルを取得している。
フロントエンド側の処理は後術している。
あとはprismaのcreate()
でdata
にtitle
とcreatedAt
として現在の日付を渡している。
DELETEでTODOを削除する
削除の場合はIDが必要となるのでAPIは、/api/todo/[id]/route.ts
に記載する。
Prisma Clientでインスタンスを作成やconnect()
で接続する関数は別ファイルにしてもいいかも。
export const DELETE = async (req: Request, { params }: { params: Params }) => {
try {
const targetId:number = Number(params.id);
await connect();
const todos = await prisma.todo.delete({
where: { id: targetId }
});
return NextResponse.json({ message: "削除成功", todos }, { status: 200 })
} catch (error) {
return NextResponse.json({ messeage: "削除失敗" }, { status: 500 })
} finally {
await prisma.$disconnect();
}
}
フロントエンド側の後で説明するとして、削除するTODOのidをAPI側に送るのは以下のようになる。
//IDが2のTODOを削除したい場合のURL
http://localhost:3000/api/todo/2
このIDを取得する為に、params.id
で受け取る。こちらはフォルダを[id]
としてるので、[num]
とかだと、params.num
となる。
また別の方法で受け取る場合は、下記のようにreq.url
でURLを取得してsplit
で分割することで所得する方法もある。
export const DELETE = async (req: Request, res: NextResponse) => {
const id: number = Number(req.url.split("/blog/")[1]);
}
受け取ったら、 prisma.todo.delete()
のwhere
でIDを指定すればいい。
あとはNextResponse.json()
でメッセージを返している。
TODO一覧を表示させる
あとはTODOの一覧を表示させてみる。
新しいNext.jsなのでサーバーコンポーネントとなっている。
一旦、分かりやすくTODOの一覧のみを記載。
const getTodoList = async () => {
const res = await fetch('http://localhost:3000/api/todo')
const json = await res.json()
return json.todos
}
type todo = {
id: number
title: string
created_at: Date
}
export default async function Home() {
const todoList = await getTodoList()
return (
<main>
<h1>Next.js + TypeScript + Prisma + supabase</h1>
{todoList.map((todo:todo) => (
<div key={todo.id}>
<h2>{todo.title}</h2>
</div>
))}
</main>
)
}
getTodoList
関数でfetch
を利用して記事を利用している。
あとはReactあるあるのmap()
でループ処理をして一覧を表示させている。
TODOの登録と削除を追加する。
今度はTODOの登録と削除を追加。
まずは登録するために登録用のフォームと処理を追加。今回はServerActionsを利用。
一覧表示と同じファイルに記載するが、分かりすくする為に必要な箇所のみ。
TODOを登録する処理
export default async function Home() {
//server actionsを利用してTODOを追加
const addPost = async (formData: FormData) => {
"use server"
const text: FormDataEntryValue | null = formData.get('text')
if (!text) return
const res = await fetch('http://localhost:3000/api/todo', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: text
})
})
revalidatePath('/')
}
return(
<form action={addPost}>
<input type="text" name="text" placeholder="New task..." />
<button>Add Task</button>
</form>
)
}
<form>
タグのaction
に実行する関数addPost
を指定する。
関数でuse server
の宣言をしてServerActionとして処理する。
フォームの値はFormData
のget()
で取得して、受け取ったテキストをfetch()
でAPI側にPOST
でデータをJSON形式で送信すると追加される。
TODOを削除する処理
今回は削除もServerActionを利用する為フォームを利用している。
export default async function Home() {
//server actionsを利用してTODOを削除
const deletePost = async (formData: FormData) => {
"use server"
const id: FormDataEntryValue | null = formData.get('id')
if (!id) return
await fetch(`http://localhost:3000/api/todo/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: id
})
})
revalidatePath('/')
}
return(
<ul className="space-y-4">
{todoList.map((todo: todo) => (
<li key={todo.id}>
<span>{todo.title}</span>
<form>
<input type="hidden" name="id" value={todo.id} />
<button formAction={deletePost}>削除</button>
</form>
</li>
))}
</ul>
)
}
TODOを削除するには、TODO一覧を取得した際に、各TODOの削除ボタン部分は<form>
タグで括って、IDをinput
要素のvalue
に指定すればいい。
非表示にするためにhidden
で非表示にする。
まとめ
一旦、これでTODOの追加・削除とTODOの一覧表示はできた。
これまでの流れを見ていただくとわかると思うが、*Supabaseを利用しているものの、Supabaseを意識した処理はほとんどなく初期設定で指定しただけである。
初期設定でMongoDBなどの設定すれば、Prismaでの処理だけで完結しそうです。
ほんとは、Supabaseにはdone
というカラムがあるので、チェックを入れた要素は取り消し線を入れるための処理を入れる予定だがまた今度。
Discussion