目次
- 環境
- フロントエンドの実装
- 動作確認
環境
フロントエンドアプリケーション
- Next v13.4.19
- react v18.2.0
- TypeScript v5.2.2
API
- Node v18.17.1
- Express v4.18.2
- TypeScript v5.2.2
DB
- Prisma v5.2.0
- mysql v8.0
ローカル環境
DockerAPIのCRUD実装
フロントエンドの実装
簡単なユーザー登録フォームを作成し、Create,Read,Update,DeleteのリクエストをAPIに送る
ディレクトリ/ファイル分割
API側同様、features/userというディレクトリをプロジェクトディレクトリの直下に作成し、細かく分けるようにする
フォームの見た目の部分
app/page.tsx
"use client";
import { useUserForm } from "@/features/user/hooks/useUserForm";
export default function Home() {
const {
name,
email,
users,
handleChange,
editUser,
submit,
findUser,
deleteUser,
} = useUserForm();
return (
<main className="flex min-h-screen flex-col items-center p-24">
<ul>
{users.length !== 0 &&
users.map((user) => (
<li key={user.id}>
{`ID: ${user.id} | 名前: ${user.name}`}{" "}
<span onClick={() => editUser(user)}>| ✏️編集 |</span>
<span onClick={() => deleteUser(user)}>🗑️削除</span>
<span onClick={() => findUser(user)}>| 詳細</span>
</li>
))}
</ul>
<div className="max-w-md mx-auto mt-5 p-5 bg-gray-100 rounded-md">
<div className="mb-4">
<label htmlFor="name" className="block text-gray-700 font-bold mb-2">
名前
</label>
<input
type="text"
id="name"
name="name"
value={name}
onChange={(e) => handleChange(e, `name`)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:border-green-500"
required
/>
</div>
<div className="mb-4">
<label htmlFor="email" className="block text-gray-700 font-bold mb-2">
メールアドレス
</label>
<input
type="email"
id="email"
name="email"
value={email}
onChange={(e) => handleChange(e, `email`)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:border-green-500"
required
/>
</div>
<div className="text-center">
<button
onClick={submit}
className="bg-green-500 hover:bg-green-600 text-white font-semibold py-2 px-4 rounded-md focus:outline-none focus:shadow-outline-green"
>
送信
</button>
</div>
</div>
</main>
);
}
Httpリクエストを送信するピュアなTypeScript関数
features/user/functions/client.ts
import { END_POINT } from "@/features/user/constant";
import { User } from "@/features/user/types/user";
import axios from "axios";
/**
* ユーザーを作成する
*/
export const create = async (name: string, email: string): Promise<User> => {
const res = await axios.post(END_POINT, {
data: { name, email },
});
return res.data.user;
};
/**
* ユーザーを取得する
*/
export const fetch = async (): Promise<User[]> => {
const res = await axios.get(END_POINT);
return res.data.users;
};
/**
* 単一のユーザーを取得する
*/
export const find = async (id: string): Promise<User> => {
const res = await axios.get(`${END_POINT}/${id}`);
return res.data.user;
};
/**
* ユーザーを更新する
*/
export const update = async (user: User): Promise<User> => {
const res = await axios.patch(END_POINT, {
data: { id: user.id, name: user.name, email: user.email },
});
return res.data.user;
};
/**
* ユーザーを削除する
*/
export const destroy = async (user: User): Promise<User> => {
const res = await axios.delete(END_POINT, {
data: { id: user.id },
});
return res.data.user;
};
stateの管理が必要なロジック
features/user/hooks/useUserForm.tsx
app/page.tsxにロジックを詰め込まないようにするためにstateが必要な処理はこちらに記述。主にfeatures/user/functions/client.tsを呼び出すだけの責務
import {
create,
destroy,
fetch,
find,
update,
} from "@/features/user/functions/client";
import { User } from "@/features/user/types/user";
import { AxiosError } from "axios";
import { useEffect, useState } from "react";
export const useUserForm = () => {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [users, setUsers] = useState<User[]>([]);
const [selectedUser, setSelectedUser] = useState<User | undefined>(undefined);
const handleChange = (
e: React.ChangeEvent<HTMLInputElement>,
key: "name" | "email"
) => {
if (key === "name") {
setName(e.target.value);
return;
}
setEmail(e.target.value);
};
const editUser = (user: User) => {
setSelectedUser(user);
setName(user.name);
setEmail(user.email);
};
const createUser = async () => {
try {
const user = await create(name, email);
setUsers((prev) => [...prev, user]);
setName("");
setEmail("");
} catch (e: unknown) {
if (e instanceof AxiosError) {
// TODO: 画面に表示
if (e.response?.data.statusCode === 400) {
console.log(e.response?.data.error);
return;
}
console.log("System error");
}
}
};
const updateUser = async (user: User) => {
const updatedUser = await update({ id: user.id, name, email });
const i = users.findIndex((u) => u.id === user.id);
const newUsers = [...users];
newUsers.splice(i, 1, updatedUser);
setUsers(newUsers);
setSelectedUser(undefined);
setName("");
setEmail("");
};
const submit = () => {
if (selectedUser) {
updateUser(selectedUser);
return;
}
createUser();
};
const findUser = async (user: User) => {
const _user = await find(user.id);
console.log(_user);
};
const deleteUser = async (user: User) => {
const deletedUser = await destroy(user);
const i = users.findIndex((u) => u.id === deletedUser.id);
const newUsers = [...users];
// i番目の要素を削除する
newUsers.splice(i, 1);
setUsers(newUsers);
};
useEffect(() => {
(async () => {
const users = await fetch();
setUsers(users);
})();
}, []);
return {
name,
email,
users,
handleChange,
editUser,
submit,
findUser,
deleteUser,
};
};
userに関する型定義
features/user/types/user.d.ts
export type User = {
id: string;
name: string;
email: string;
};
userに関する定数類
features/user/constant.ts
export const END_POINT = "http://localhost:18000/users";
動作確認
新規作成
取得
更新
削除
Part3はここまで。Part4では主にAPIの簡単なエラーハンドリングを行う
まとめ
いかがでしたでしょうか。本記事は、ExpressとPrismaでAPIをサクッと作るシリーズのPart3です。Part3では主にフロントエンドアプリケーションをNext.jsで作成し、APIへhttpリクエストを送信しています。Part4では主にAPIの簡単なエラーハンドリングを行います