はじめに
筆者の所属企業はグループ展開しており、各社の社員が自由に使用できる会議室やフリースペースが本社内に3つほど設けられています。
それら会議室などの予約システムとしてPerl
×CGI
の仕組みで動くライブラリを使っているのですが、UI含めて古くなってきており社内でメンテナンスできる人材もいない状態で長年使用されてきました。
そこで今回、予約システムをモダナイゼーションしようと昨年12月ごろから本格的に開発を進めてきました。パートナーにしたのは主にclaude
です。
開発したものは以下の2パターンで、本記事では「1:開発・テスト用」を紹介したいと思います。
- 開発・テスト用:
Next.js
(v15)×prisma
×SQLite
- 本番用:
Next.js
(v15)×prisma
×PostgreSQL
というのも、当初SQLite
で完結しようと思っていたのですがNext.js
のAPI操作(今回Route Handlers
を使用)を行うのにサーバーサイドの実行環境が必要で、いつも使っているホスティング先では対応していなかったので「2:本番用」も作成した次第です。
内容としては従来使っていた予約システムに沿った以下機能となります。
- 各部屋ごとに予約・編集できる
- 編集作業は予約時に一緒に登録したパスワードで制御
- 各部屋の予約時間を視覚的に確認できるタイムテーブル
- 予約時間に関する検証機能(他者との予約重複や指定時間帯外への登録)
技術構成
├── @prisma/client@6.2.1
├── @types/node@20.16.11
├── @types/react-dom@19.0.2
├── @types/react@19.0.1
├── @types/uuid@10.0.0
├── eslint-config-next@15.1.1
├── eslint@8.57.1
├── jotai@2.10.0
├── next@15.1.3
├── prisma@6.2.1
├── react-dom@19.0.0
├── react@19.0.0
├── typescript@5.6.2
└── uuid@10.0.0
作成した予約システムのキャプチャ
-
登録
-
編集
開発面で意識したこと
UIなどは適宜変更しつつも、従来の予約システムと同じ機能の実装は絶対としました。
先ほども掲載しましたが、従来の予約システムの機能は以下となります。
- 各部屋ごとに予約・編集できる
- 編集作業は予約時に一緒に登録したパスワードで制御
- 各部屋の予約時間を視覚的に確認できるタイムテーブル
- 予約時間に関する検証機能(他者との予約重複や指定時間帯外への登録)
従来の予約システムでは、編集作業や各部屋の予約内容を確認するには各部屋ページに遷移しないと操作・確認できませんでした。
そこで今回は、一画面(会議室予約画面)で登録・確認・編集が行えるUIに変更しました。
部屋と予約時間の管理
各部屋と時間帯は以下のように一ファイルで一元管理しています。
import { atom } from "jotai";
import { roomsType } from "../components/rooms/ts/roomsType";
export const timeBlockBegin: number = 9; // 予約可能-開始時間
export const timeBlockEnd: number = 21; // 予約可能-終了時間
//「:」より後の文字がスケジュールテーブルに表示されます
const rooms: roomsType = [
{ room: '会議室:2F' },
{ room: '多目的ホール:3F' },
{ room: '応接室:4F' }
];
export const roomsAtom = atom<roomsType>(rooms);
各部屋はデータベースでテーブル管理しているわけではないので、部屋数や時間帯などは上記ファイルで柔軟かつ容易に変更できるようになっています。
ちなみに、テーブルの中身は以下です。
model Reservation {
id String @id @default(uuid()) // 主キーの指定(UUID)
todoID String // 日付 (yyyy/mm/d)
todoContent String // 予約内容
edit Boolean @default(false)
pw String // 編集可否パスワード
person String // 予約者名
rooms String // 予約会議室名
startTime String // 開始時間 (hh:mm)
finishTime String // 終了時間 (hh:mm)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
上記内容のオブジェクトを予約リストとしてjotai
で状態管理しています。
また、今回の開発で「非同期の初期値を扱えるatomWithDefault
」を初めて知りました。
atomWithDefault
について
atomWithDefault
:非同期の初期値を扱えるjotai
のユーティリティ。引数として渡した非同期関数(例:fetch
関数など)が実行され、その結果がatom
の初期値として設定される。
export const fetchTodoMemoAtom = atomWithDefault(async () => {
const API_URL = process.env.NEXT_PUBLIC_API_URL;
// GET処理: api/reservations/route.ts の GET によりDBからのデータを取得
const response: Response = await fetch(`${API_URL}api/reservations`, { cache: 'no-store' });
const resObj: todoItemType[] = await response.json();
return resObj;
});
const [fetchTodoMemo] = useAtom(fetchTodoMemoAtom);
...
..
useEffect(() => {
if (fetchTodoMemo.length > 0) {
const exceptPastTodoMemos: todoItemType[] = [...fetchTodoMemo].filter(memo => {
const memoDate: number = parseInt(memo.todoID.replaceAll('/', ''));
if (memoDate >= present) {
return memo;
} else {
/* 過去分はDBから削除 */
deleteReservation(memo.id);
}
});
/* 当日以降の予定のみスケジュールとして管理・把握 */
setTodoMemo(exceptPastTodoMemos);
}
}, []);
atomWithDefault
は、コンポーネント内で useAtom(atomWithDefaultで設定したatom)
を使用すると、自動的に以下の処理を行うようです。
- フェッチ処理が実行される
- データが取得できるまでの間は
undefined
またはPromise
の状態になる - データ取得完了後、取得したデータで状態が更新される
Prisma
×SQLite
の設定
こちらのGitHub
リポジトリをgit clone
またはzip
ダウンロードしてnpm install
していただければ直ぐに使えるようになっていますが、自身の備忘録や情報共有の観点からPrisma
×SQLite
の設定を一から行う場合のフローを残します。
※本記事の予約システムを試したい方は上記方法で自由に触ってみてください。
これから予約システムに関するAPI操作やUIについても書いていくので、Prisma
×SQLite
の設定に関して特に興味のない方はデータのCRUD(API操作)についてや各種フォーム項目の更新、UIについて、データベースの仕様テーブル更新などに飛んでください。
-
Prisma
とSQLite
について簡潔なおさらい-
Prisma
Prisma
は、データベースとのやり取りを簡単にするORM
というツールです。ORM
とはデータベースのテーブルをオブジェクトとして操作できる技術で、SQL
を書かなくてもJavaScript
(TypeScript
)のコードだけでデータベース操作ができるようになる代物です。 -
SQLite
SQLite
は、軽量で組み込み型のリレーショナルデータベースです。単一のファイルでデータベースを管理できるため、ファイルをコピーするだけで簡単にデータベースを移行できます。つまり、プロジェクト内でデータベースを持てるので外部にサーバーを立てる必要がありません。本番用ではなく、一般的にはモックやプロトタイプ、小規模なプロジェクトで使用されます。
-
Prisma
の設定
以下のフローでPrisma
をインストール&設定していきます。
この記事内の「4. データの永続化をする」が参考になりました。
-
Prisma
のインストール# Prisma のプロジェクトを初めてセットアップするケース # CLI ツールとクライアントの両方をインストール npm install prisma @prisma/client # Prisma クライアントをインストールまたは更新するだけのケース # たとえば、本番環境やすでに Prisma CLI をセットアップ済みの場合 npm install @prisma/client
-
Prisma
の初期化npx prisma init
-
マイグレーションの実行
npx prisma migrate dev --name init
※マイグレート(設定した内容を実際のデータベースに反映させる作業)
-
クライアントの生成
npx prisma generate
-
.env
と.env.local
の設定-
.env
DATABASE_URL="file:./dev.db"
-
.env.local
# 今回 Next.js なので NEXT_PUBLIC を使用 NEXT_PUBLIC_API_URL="http://localhost:3000/"
-
蛇足ですが環境変数にNEXT_PUBLIC
を前置したものはクライアントサイドに露出するのでセンシティブな情報(例:APIキーなど)には付けないよう気をつけたいところです。
ここまででインストール&設定は済んだので、次はPrisma
利用時におけるデータベースの種別やテーブル設定に進みます。
データベースの種別やテーブル設定
スキーマ設定
prisma/schema.prisma
datasource db {
provider = "sqlite" // 使用するDBの種類を指定(今回はSQLite)
url = "file:./dev.db" // プロジェクト内の dev.db をデータベースの参照URLとして設定
}
generator client {
provider = "prisma-client-js" // Prismaクライアントを生成するためのライブラリを指定
}
// データベースのテーブル内容とリンクさせるための設定
model Reservation {
id String @id @default(uuid()) // 主キーの指定(UUID)
todoID String // 日付 (yyyy/mm/d)
todoContent String // 予約内容
edit Boolean @default(false)
pw String // 編集可否パスワード
person String // 予約者名
rooms String // 予約会議室名
startTime String // 開始時間 (hh:mm)
finishTime String // 終了時間 (hh:mm)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
prisma
クライアントの設定
src/lib/prisma.ts
/* クライアントで prisma を通じてデータベースを操作・利用するための機能をインポート */
import { PrismaClient } from '@prisma/client';
/* グローバルスコープに PrismaClient のインスタンスを保持するための型定義 */
const globalForPrisma = global as unknown as { prisma: PrismaClient };
/* PrismaClient のインスタンスが存在しない場合は新規作成 */
export const prisma = globalForPrisma.prisma || new PrismaClient();
/* 開発環境の場合のみ、グローバルオブジェクトに PrismaClient インスタンスを保持 */
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
ここまで進めるとPrisma
×SQLite
の設定は完了になります!
prisma studio
Prisma
には、prisma studio
というGUI
でテーブル操作できる機能があります。
npx prisma studio
GUI
でパパっと手っ取り早くテーブル操作したい場合に便利です。
データのCRUD(API操作)について
今回Route Handlers
でCRUDを実現しています。
-
src/app/api/reservations/route.ts
データの取得と投稿の処理
import { prisma } from '@/lib/prisma';
import { NextResponse } from 'next/server';
import { todoItemType } from '@/app/components/schedule/todoItems/ts/todoItemType';
// GET(取得)
export async function GET() {
const reservations = await prisma.reservation.findMany();
return NextResponse.json(reservations);
}
// POST(投稿)
export async function POST(request: Request) {
const data: todoItemType = await request.json();
const reservation = await prisma.reservation.create({
/**
* POST 操作において id は記述不要
* prisma\schema.prisma で主キーとして id(uuid)を指定しているので data 内に記述すると重複処理でエラーとなる
*/
data: {
todoID: data.todoID,
todoContent: data.todoContent,
edit: data.edit,
pw: data.pw,
person: typeof data.person !== 'undefined' ? data.person : '',
rooms: typeof data.rooms !== 'undefined' ? data.rooms : '',
startTime: typeof data.startTime !== 'undefined' ? data.startTime : '',
finishTime: typeof data.finishTime !== 'undefined' ? data.finishTime : '',
},
});
return NextResponse.json(reservation);
}
-
src/app/api/reservations/[id]/route.ts
データの更新と削除の処理
import { prisma } from "@/lib/prisma";
import { NextResponse } from "next/server";
import { todoItemType } from "@/app/components/schedule/todoItems/ts/todoItemType";
// PUT(更新)
export async function PUT(request: Request) {
const data: todoItemType = await request.json();
const id: string = request.url.split('/reservations/')[1];
if (!id) {
return NextResponse.json({ error: 'ID is required' }, { status: 400 });
}
const reservation = await prisma.reservation.update({
where: {
id: id,
},
data: {
todoID: data.todoID,
todoContent: data.todoContent,
edit: data.edit,
pw: data.pw,
person: typeof data.person !== 'undefined' ? data.person : '',
rooms: typeof data.rooms !== 'undefined' ? data.rooms : '',
startTime: typeof data.startTime !== 'undefined' ? data.startTime : '',
finishTime: typeof data.finishTime !== 'undefined' ? data.finishTime : '',
},
});
return NextResponse.json(reservation);
}
// DELETE(削除)
export async function DELETE(request: Request) {
const id: string = request.url.split('/reservations/')[1];
if (!id) {
return NextResponse.json({ error: 'ID is required' }, { status: 400 });
}
await prisma.reservation.delete({
where: {
id: id,
},
});
return NextResponse.json({ message: 'Deleted successfully' });
}
prisma.reservation.findMany
やprisma.reservation.create
, prisma.reservation.update
, prisma.reservation.delete
といった簡潔な記述でCRUDが行える(データベースのデータを扱える)ことに「prisma
ってすごー!ほんとにSQL
書かずに済んでるやん」という驚きと感動を覚えました。
ちなみに、投稿や更新、削除は以下のようにカスタムフックに切り分けて実装しています
投稿や更新、削除に関する記述
- 投稿
import { v4 as uuidv4 } from 'uuid'; // key へ渡すための固有の識別子を生成する npm ライブラリ
import { todoItemType } from "../ts/todoItemType";
import { useAtom } from "jotai";
import { todoMemoAtom } from '@/app/types/calendar-atom';
export const useRegiTodoItem = () => {
const [todoMemo, setTodoMemo] = useAtom(todoMemoAtom);
/* データベース(SQLite)に予約を登録 */
const createReservation: (data: todoItemType) => Promise<todoItemType> = async (data: todoItemType) => {
const response = await fetch('/api/reservations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
return response.json();
};
/* ToDo(予約)の登録 */
const regiTodoItem: (todoItems: todoItemType) => void = (todoItems: todoItemType) => {
const shallowCopyTodoItems: todoItemType = { ...todoItems }
const newTodoList: todoItemType = {
...shallowCopyTodoItems,
id: uuidv4() // key へ渡すための固有の識別子(uuid:Universally Unique Identifier)を生成
}
if (shallowCopyTodoItems.todoContent.length > 0) {
createReservation(newTodoList);
setTodoMemo([...todoMemo, newTodoList]);
location.reload(); // 登録直後に当該内容を更新すると 500エラーになるため再読み込みさせて登録完了させておく
}
}
return { regiTodoItem }
}
- 更新
import { todoItemType } from "../ts/todoItemType";
import { useAtom } from "jotai";
import { todoMemoAtom } from "@/app/types/calendar-atom";
export const useUpdateTodoItem = () => {
const [todoMemo, setTodoMemo] = useAtom(todoMemoAtom);
/* データベース(SQLite)の当該予約を更新 */
const updateReservation: (data: todoItemType) => Promise<todoItemType> = async (data: todoItemType) => {
const response = await fetch(`/api/reservations/${data.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
return response.json();
};
/* ToDo(予約)の更新 */
const updateTodoItem: (todoItems: todoItemType) => void = (todoItems: todoItemType) => {
const updateTodoList: todoItemType = { ...todoItems };
const exceptRemoveTodoItems: todoItemType[] = [...todoMemo].filter(todoItem => todoItem.id !== updateTodoList.id); // 今回更新(削除)対象の todoItem 以外を返す
if (updateTodoList.todoContent.length > 0) {
updateReservation(updateTodoList);
setTodoMemo([...exceptRemoveTodoItems, updateTodoList]);
}
}
return { updateTodoItem, updateReservation }
}
- 削除
import { todoItemType } from "../ts/todoItemType";
import { useAtom } from "jotai";
import { todoMemoAtom } from "@/app/types/calendar-atom";
export const useDeleteTodoItem = () => {
const [todoMemo, setTodoMemo] = useAtom(todoMemoAtom);
/* データベース(SQLite)から当該予約を削除 */
const deleteReservation: (id: string) => Promise<void> = async (id: string) => {
await fetch(`/api/reservations/${id}`, {
// delete なので DELETE、データの扱いに関する記述(headers, body, etc...)は不要
method: "DELETE"
});
}
const deleteTodoItem: (id: string) => void = (id: string) => {
deleteReservation(id);
const exceptRemoveTodoItems: todoItemType[] = [...todoMemo].filter(todoItem => todoItem.id !== id);
setTodoMemo(exceptRemoveTodoItems);
}
return { deleteTodoItem, deleteReservation }
}
各種フォーム項目の更新
部屋や時間帯、予約内容などフォームを通じて登録する各項目に関しては、以下の汎用的なカスタムフックを用意して処理しています。
import { ChangeEvent } from "react";
import { useHandleInputValueSanitize } from "./useHandleInputValueSanitize";
type handleFormEntriesType = <T>(
targetElm: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
targetFormEntries: T,
setEntries: React.Dispatch<React.SetStateAction<T>>
) => void
export const useHandleFormEntries = () => {
// サニタイズしたいラベルを用意(input の id属性名)
const isCheckIdAttr_forSanitize = ['todoContent', 'pw'];
const { handleInputValueSanitize } = useHandleInputValueSanitize();
/* <T>:ジェネリクスで任意の型を指定 */
const handleFormEntries: handleFormEntriesType = function <T>(
targetElm: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>,
targetFormEntries: T,
setEntries: React.Dispatch<React.SetStateAction<T>>
): void {
// id属性からプロパティ名を取得
const type: string = targetElm.currentTarget.id;
let value: string | number | boolean = targetElm.currentTarget.value;
// サニタイズが必要なラベルには実施
if (isCheckIdAttr_forSanitize.includes(type)) {
value = handleInputValueSanitize(targetElm.currentTarget.value);
}
const newEntries: T = {
...targetFormEntries,
[type]: value // id属性と一致するkeyの値にvalueをセット
}
setEntries(newEntries);
}
return { handleFormEntries }
}
- コンポーネントでの使用例
<label>
<span>予定内容</span>
<input
type="text"
value={todoItems.todoContent}
id="todoContent"
onInput={
(e: ChangeEvent<HTMLInputElement>) =>
handleFormEntries<todoItemType>(e, todoItems, setTodoItems)
}
/>
</label>
UIについて
- 当日以前(過去)の日付には予約用アイコンを表示しない(予約可能箇所の明示化)
- スケジュール表の当日日付には背景色を付けて差別化
- スマホなど表示幅が狭い端末ではタイムテーブルを横スクロール表示
- タイムテーブルの各時間帯には15分刻みでラインを入れて判別性を向上
- 当日以前(過去)の予約内容の自動削除機能
上記で列挙したようにこだわった点はたくさんあるものの、実装含めて苦労したのは「各部屋の予約時間を視覚的に確認できるタイムテーブル」部分でした。
// TimeTable.tsx
import { timeBlockBegin, timeBlockEnd } from "@/app/types/rooms-atom";
function TimeTable({ room, todoMemo }: { room: string, todoMemo: todoItemType[] }) {
const timeBlocks: number[] = [];
// timeBlockBegin:開始時間(9時)〜 timeBlockEnd:終了時間(21時)までの時間帯の配列を用意
for (let i = timeBlockBegin; i < timeBlockEnd; i++) timeBlocks.push(i);
return (
<ul>
{timeBlocks.map(timeBlock => (
<li key={timeBlock}>
<span>{timeBlock}</span>
<div className={roomStyle.minBlocks}>
{/* 各時間の分を表示するコンポーネント */}
<TimeBlock props={{
room: room,
timeBlock: timeBlock,
todoMemo: todoMemo
}} />
</div>
</li>
))}
</ul>
);
}
export default memo(TimeTable);
// TimeBlock.tsx
function TimeBlock({ props }: { props: TimeBlockType }) {
const { room, timeBlock, todoMemo } = props;
const minBlocks: number[] = [];
for (let i = 1; i <= 59; i++) minBlocks.push(i);
const today: string = useMemo(() => `${new Date().getFullYear()}/${new Date().getMonth() + 1}/${new Date().getDate()}`, []);
// 動的な予約情報(当日限定及び各部屋ごとのタイムテーブル配列)の取得
const relevantReservations: todoItemType[] = useMemo(() => {
return [...todoMemo].filter(memo =>
(memo.todoID === today) &&
(typeof memo.rooms !== 'undefined' && memo.rooms === room)
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [todoMemo, room]);
// some 処理によって一つでも true なら true が返却される
const reservedFlag: (timeBlock: number, minBlock: number) => boolean = (timeBlock: number, minBlock: number) => {
return relevantReservations.some(reservation => {
const theTime = parseInt(`${timeBlock}${minBlock.toString().padStart(2, '0')}`);
const start = parseInt(reservation.startTime?.split(':').join('') ?? '0');
const finish = parseInt(reservation.finishTime?.split(':').join('') ?? '0');
return theTime >= start && theTime <= finish;
});
};
return (
<>
{minBlocks.map(minBlock => (
<div
key={minBlock}
data-minblock={minBlock}
data-reserved={reservedFlag(timeBlock, minBlock)}
> </div>
))}
</>
);
}
export default memo(TimeBlock);
やっていることはすごく地味で、(時間と)分の配列を用意し、各自を予約開始と終了時間とで比較して予約時間内の場合はdata-reserved
をtrue
にする仕組みです。data-reserved
がtrue
の場合のスタイルを別途用意することで視覚的なUIを実現しています。
1.(時間と)分の配列を用意
const minBlocks: number[] = [];
for (let i = 1; i <= 59; i++) minBlocks.push(i);
2.各自を予約開始と終了時間とで比較
// some 処理によって一つでも true なら true が返却される
const reservedFlag: (timeBlock: number, minBlock: number) => boolean = (timeBlock: number, minBlock: number) => {
return relevantReservations.some(reservation => {
const theTime = parseInt(`${timeBlock}${minBlock.toString().padStart(2, '0')}`);
const start = parseInt(reservation.startTime?.split(':').join('') ?? '0');
const finish = parseInt(reservation.finishTime?.split(':').join('') ?? '0');
return theTime >= start && theTime <= finish;
});
};
...
..
.
// 予約時間内の場合は`data-reserved`を`true`
{minBlocks.map(minBlock => (
<div
key={minBlock}
data-minblock={minBlock}
data-reserved={reservedFlag(timeBlock, minBlock)}
> </div>
))}
3.data-reserved
がtrue
の場合のスタイルを別途用意
.minBlocks {
& div {
width: 1px;
&[data-reserved=true] {
background-color: #b6f7ba;
}
}
}
これらロジック面は自分でまかなえたものの、「当日の予約内容のみ表示」する以下実装においてclaude
に助けてもらいました。
const today: string = useMemo(() => `${new Date().getFullYear()}/${new Date().getMonth() + 1}/${new Date().getDate()}`, []);
// 動的な予約情報(当日限定及び各部屋ごとのタイムテーブル配列)の取得
const relevantReservations: todoItemType[] = useMemo(() => {
return [...todoMemo].filter(memo =>
(memo.todoID === today) &&
(typeof memo.rooms !== 'undefined' && memo.rooms === room)
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [todoMemo, room]);
シンプルに予約リスト(todoMemo
)を軸にして処理を進めれば良いのに、別途タイムテーブル用の変数を設けて処理させる冗長かつ複雑な実装を行っていたせいで意図したUIを実現できずにいました。
(自身のスキル不足は一旦置いておいて) こういった点でも生成AIを活用した便利な開発体験を実感しました。
最後に、データベースの仕様(テーブル)更新の方法を紹介したいと思います。
データベースの仕様(テーブル)更新
登録している現状の内容(テーブル)から特定項目を削除したり、追加したりといった変更は以下のように行います。
-
prisma/schema.prisma
model
オブジェクトの内容を編集(登録内容を追加・削除) -
prisma/schema.prisma
のmodel
オブジェクト編集後、以下のコマンドをターミナルに打つ
# マイグレーションファイルを作成し、データベースに変更を適用
npx prisma migrate dev --name what_you_changed # --name 以降は任意の命名
# Prismaクライアントを更新して新しいスキーマを反映
npx prisma generate
-
prisma/dev.db-journal
dev.db-journal
というSQLite
の内部処理用ファイルが自動的に生成・削除されますが無視して構いません。
dev.db-journal
はSQLite
が自動的に管理するSQLite
のトランザクションログファイルで、データベース操作の一時的な記録を保持しています。
※本記事で紹介している予約システムのテーブル更新を行う場合は更に以下のフローが必要になります。
-
src/app/components/schedule/todoItems/ts/todoItemType.ts
登録内容の型情報を編集 -
src/app/components/schedule/todoItems/TodoForm.tsx
-
todoItems
ステートの初期値であるinitTodoItems
オブジェクトを編集(オブジェクトに当該登録内容であるプロパティ・キーを追加・削除) - (変更した)当該登録内容に関する入力フォームを(
src/app/components/schedule/todoItems/utils
配下に)用意または調整
-
-
src/app/api/reservations/
配下のRoute Handlers
の登録内容を編集-
POST
,PUT
に関するdata
オブジェクト内を編集(例:プロパティ・キーの追加など)- ※
data
オブジェクト編集後に型エラーが表示される場合は一旦VSCode
を閉じてみる
- ※
-
- 上記フローを経ても予約登録機能が動かない場合
上記フローを経て、git pull origin main
で当該リモートリポジトリと整合性を取ったのに予約登録機能が動かない場合は以下のコマンドをターミナル
で打つ。
※ WindowsPC でコマンドを実行した際に権限上のエラーが発生した場合はコマンドプロンプト
で再度試してみる。
# Prismaクライアントを更新して新しいスキーマを反映
npx prisma generate
さいごに
今回の開発ではNext.js
(v15)を実際に使ってみたことに加えて、以前から気になっていたORM
(prisma
)やSQLite
も触れたので一石三鳥のようなものでした。
しかし冒頭でも書いた通り本来はNext.js
(v15)×prisma
×SQLite
で完結するつもりだったのが、ホスティング先のサーバーサイドの実行環境の有無からNext.js
(v15)×prisma
×PostgreSQL
での開発に切り替えました。
本番用のホスティング先はVercel
にしたのですが、データベース(PostgreSQL
)の用意や設定、デプロイにおける設定、別PCでの開発環境設定など本記事の内容と諸々勝手が異なる部分も多々ありました。
そちらに関しては別の記事で紹介しています。
筆者としてはキャッチアップになったので良かったですが、やはりSQLite
は開発やテスト、プロトタイプとしての用途が強いように感じます。
本格的な運用を前提とする場合は初めからSupabase
などBaaS
を利用したり、MySQL
やPostgreSQL
などデータベースを用意したりするのが無難だと感じました。
本記事に関して、なにか間違いや気になる点があればご教授いただけますとありがたく存じます!
ちなみに、記事の途中でも述べたように今回作った予約システムに関するGitHub
は自由に使っていただいて結構ですので関心のある方はどうぞです。