🐸
Nuxt3 × Prisma Todoアプリ開発
簡単なTODOアプリ開発
フロントのUIはTailwind CSSとElementPlusを使用します。
導入手順は以下参照
フロント側のUIから作成していきます。
app.vue
<script setup lang="ts">
// 入力値
const todo = ref<string>("");
// TODOを格納する配列
const todoList = ref<string[]>([]);
// TODOリストに追加
const addTodo = () => {
if (todo.value.trim() === "") {
return;
}
todoList.value.push(todo.value);
todo.value = "";
};
// TODOリストから削除
const removeTodo = (index: number) => {
todoList.value.splice(index, 1);
};
</script>
<template>
<div
class="flex flex-col items-center justify-center min-h-screen bg-gray-100 w-full"
>
<h1 class="text-5xl font-bold text-slate-700 mb-8">TODO App</h1>
<div class="w-96">
<el-row :gutter="24">
<el-col :span="20">
<el-input v-model="todo" placeholder="Please input" size="large" />
</el-col>
<el-col :span="4">
<el-button
type="primary"
class="text-2xl"
size="large"
@click="addTodo"
>
追加
</el-button>
</el-col>
</el-row>
<template v-if="todoList.length > 0">
<el-row>
<el-col :span="12">
<el-card class="w-[410px] mt-4">
<div
v-for="(item, index) in todoList"
:key="index"
class="text-lg flex justify-between items-center py-2"
>
<span>{{ item }}</span>
<el-button type="danger" @click="removeTodo(index)"
>削除</el-button
>
</div>
</el-card>
</el-col>
</el-row>
</template>
</div>
</div>
</template>
よく見るTODOアプリだと思うので解説はしません。
追加と削除の機能だけ実装しています。
初期画面
TODO追加ver
Prismaを導入
npm install prisma --save-dev
npm install @prisma/client
npx prisma init
Prisma スキーマ作成
prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Todo {
id Int @id @default(autoincrement())
title String
createdAt DateTime @default(now())
}
マイグレーション & DB 作成
npx prisma migrate dev --name init
Prisma クライアント共通ファイル作成
server/utils/prisma.ts
// PrismaClient クラスを @prisma/client からインポート
import { PrismaClient } from "@prisma/client";
// TypeScript の型定義を使って、Node.js のグローバルスコープに
// prisma を保存できるように準備(再利用のため)
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
// Prisma クライアントのインスタンスを作成または再利用する
// 開発環境ではファイル変更時にモジュールが何度も再読み込みされるため、
// new PrismaClient() を何回も呼ぶと DB 接続エラーが出る(接続数オーバー)
// それを防ぐため、一度作成したクライアントをグローバルに保存して使い回す
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
// 開発時に発行される SQL クエリをログとして出力(デバッグ用)
log: ["query"],
});
// 本番環境でなければ、作成した PrismaClient を globalThis に保存
// 開発中にファイルが更新されても、同じインスタンスを再利用できるようになる
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
API エンドポイント作成
server/api/todos/index.get.ts
// グローバルで使い回す Prisma クライアントをインポート
import { prisma } from "~/server/utils/prisma";
// defineEventHandler は Nuxt3 の API エンドポイントを定義する関数
// この関数が呼ばれると、非同期でTodoの一覧を返す
export default defineEventHandler(async () => {
// Prismaを使ってtodoテーブルのすべてのレコードを取得
// createdAt(作成日時)の降順(新しい順)で並び替え
return await prisma.todo.findMany({
orderBy: {
createdAt: "desc", // 日付が新しい順に表示
},
});
});
server/api/todos/create.post.ts
// グローバルで使い回される Prisma クライアントをインポート
import { prisma } from "~/server/utils/prisma";
// Nuxt3 の API エンドポイントを定義(POSTリクエストなどを受け付ける)
export default defineEventHandler(async (event) => {
// クライアントから送られてきたリクエストのボディを読み取る
// この中に { title: "xxx" } のようなデータが含まれている前提
const body = await readBody(event);
// Prisma を使って Todo テーブルに新しいレコードを作成する
return await prisma.todo.create({
data: {
// クライアントから受け取ったタイトルをDBに保存
title: body.title,
},
});
});
server/api/todos/[id].delete.ts
// グローバルで使い回す Prisma クライアントをインポート
import { prisma } from "~/server/utils/prisma";
// Nuxt3 の API エンドポイントを定義
export default defineEventHandler(async (event) => {
// URL パラメータ(例: /api/todos/3 の「3」)を取得し、数値に変換
const id = Number(getRouterParam(event, "id"));
// 数字でないIDが渡された場合、400エラーを返す(バリデーション)
if (isNaN(id)) {
throw createError({ statusCode: 400, statusMessage: "Invalid ID" });
}
// Prisma を使って、指定されたIDのTodoを削除
return await prisma.todo.delete({
where: { id }, // idが一致するレコードを削除
});
});
フロント修正
app.vue
<script setup lang="ts">
// interface
interface TodoList {
id: number;
title: string;
}
// 入力値
const todo = ref<string>("");
// TODOを格納する配列
const todoList = ref<TodoList[]>([]);
// TODOリストに追加
const addTodo = async () => {
try {
if (!todo.value.trim()) return;
await $fetch("/api/todos/create", {
method: "POST",
body: { title: todo.value },
});
todo.value = "";
await fetchTodos();
} catch (error) {
console.error(error);
}
};
// TODOリストから削除
const deleteTodo = async (id: number) => {
try {
await $fetch(`/api/todos/${id}`, { method: "DELETE" });
await fetchTodos();
} catch (error) {
console.error(error);
}
};
const fetchTodos = async () => {
try {
todoList.value = await $fetch<TodoList[]>("/api/todos");
} catch (error) {
console.error(error);
}
};
onMounted(async () => {
await fetchTodos();
});
</script>
<template>
<div class="flex flex-col items-center min-h-screen bg-gray-100 w-full pt-24">
<h1 class="text-5xl font-bold text-slate-700 mb-8">TODO App</h1>
<div class="w-96">
<el-row :gutter="24">
<el-col :span="20">
<el-input v-model="todo" placeholder="Please input" size="large" />
</el-col>
<el-col :span="4">
<el-button
type="primary"
class="text-2xl"
size="large"
@click="addTodo"
>
追加
</el-button>
</el-col>
</el-row>
<template v-if="todoList.length > 0">
<el-row>
<el-col :span="12">
<el-card class="w-[410px] mt-4">
<div
v-for="todo in todoList"
:key="todo.id"
class="text-lg flex justify-between items-center py-2"
>
<span class="ml-2">{{ todo.title }}</span>
<el-button type="danger" @click="deleteTodo(todo.id)"
>削除</el-button
>
</div>
</el-card>
</el-col>
</el-row>
</template>
</div>
</div>
</template>
これで、簡易ですがTODOアプリができました。
はじめてPrismaを使ってみたので間違えていたらすみません
Discussion