目次
- 環境
- Httpリクエストの準備
- APIのCRUD実装
- まとめ
環境
API
- Node v18.17.1
- Express v4.18.2
- TypeScript v5.2.2
DB
- Prisma v5.2.0
- mysql v8.0
ローカル環境
Docker
Httpリクエストの準備
post manやThunder Client等のクライアントライブラリを用意すること。フロントエンドの実装はPart3で行うため、それまではこちらからリクエストのテストを実施する
https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client
APIのCRUD実装
前準備
ファイル、ディレクトリ準備
細かくディレクトリやファイルを分けたいので、その準備を行う(不要な場合はスキップしてもらってもOK)
modulesディレクトリを作成しその中に責務ごとにファイルを配置
// ユーザー関連の機能を集約するディレクトリ
mkdir -p modules/user
mkdir modules/user/{repository,routes,useCase,utils}
// DB層へのアクセス
touch modules/user/{repository/UserRepository.ts,repository/UserRepositoryImpl.ts}
// エンドポイント定義
touch modules/user/routes/user.ts
// ユーザーのリソースに対する具体的なユースケース(時間の関係上、createとfindのみ実装。他はTODO)
touch modules/user/{useCase/CreateUserUseCase.ts,useCase/FindUserUseCase.ts}
// バリデーションやエラーハンドラーなどのutil
touch modules/user/{utils/errorHandler.ts,utils/validation.ts}
expressの処理を分割
api/main.tsに全て突っ込むと必ずゴチャつくので、スッキリさせたい。インスタンス生成は、api/config/express.tsに、ルーティング定義は、api/modules/user/routes/user.tsに定義する
api/main.ts
import app from "./config/express";
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Running at Port ${port}...`);
});
api/config/express.ts
import express, { Application } from "express";
import cors from "cors";
import user from "../modules/user/routes/user";
const app: Application = express();
app.use(express.json());
const allowedOrigins = ["http://localhost:3000"];
const options: cors.CorsOptions = {
origin: allowedOrigins,
};
app.use(cors(options));
app.use("/users", user);
export default app;
api/modules/user/routes/user.ts
import express, { Request, Response } from "express";
const router = express.Router();
/**
* ユーザー新規作成
*/
router.post("", async (req: Request, res: Response) => {
// 後ほど実装
});
/**
* ユーザー一覧取得
*/
router.get("", async (req: Request, res: Response) => {
// 後ほど実装
});
/**
* ユーザー取得
*/
router.get("/:id", async (req: Request, res: Response) => {
// 後ほど実装
});
/**
* ユーザー更新
*/
router.patch("", async (req: Request, res: Response) => {
// 後ほど実装
});
/**
* ユーザー削除
*/
router.delete("", async (req: Request, res: Response) => {
// 後ほど実装
});
export default router;
Create
実装
api/modules/user/routes/user.ts
import express, { Request, Response } from "express";
const router = express.Router();
/**
* ユーザー新規作成
*/
router.post("", async (req: Request, res: Response) => {
// 追加
const { email, name } = req.body.data;
const useCase = new CreateUserUseCase();
const user = await useCase.create(name, email);
return res.status(200).json({
user,
});
});
/**
* ユーザー一覧取得
*/
router.get("", async (req: Request, res: Response) => {
// 後ほど実装
});
/**
* ユーザー取得
*/
router.get("/:id", async (req: Request, res: Response) => {
// 後ほど実装
});
/**
* ユーザー更新
*/
router.patch("", async (req: Request, res: Response) => {
// 後ほど実装
});
/**
* ユーザー削除
*/
router.delete("", async (req: Request, res: Response) => {
// 後ほど実装
});
export default router;
api/modules/user/useCase/CreateUserUseCase.ts
import { User } from "@prisma/client";
import { UserRepository } from "../repository/UserRepository";
import { UserRepositoryImpl } from "../repository/UserRepositoryImpl";
export class CreateUserUseCase {
private userRepository: UserRepository;
public constructor() {
// TODO: DI
this.userRepository = new UserRepositoryImpl();
}
public async create(name: string, email: string): Promise<User | null> {
return await this.userRepository.save(name, email);
}
}
api/modules/user/repository/UserRepository.ts
import { User } from "@prisma/client";
export interface UserRepository {
save(name: string, email: string): Promise<User>;
}
api/modules/user/repository/UserRepositoryImpl.ts
import { PrismaClient, User } from "@prisma/client";
import { UserRepository } from "./UserRepository";
export class UserRepositoryImpl implements UserRepository {
async save(name: string, email: string): Promise<User> {
const prisma = new PrismaClient();
const user = await prisma.user.create({
data: {
email,
name,
},
});
return user;
}
}
リクエスト検証
Thunder Clientを開き、POST、urlをhttp://localhost:18000/usersを指定。Sendをクリックし、右側のステータスが200で作成したユーザー情報が返ってきていればOK。送信するjsonは以下のような感じ
{
"data" : {
"name": "hoge",
"email": "hoge@gmail.com"
}
}
適当に、もう一人ユーザーを追加しておく
Read
実装
api/modules/user/routes/user.ts
import express, { Request, Response } from "express";
import { CreateUserUseCase } from "../useCase/CreateUserUseCase";
import { FindUserUseCase } from "../useCase/FindUserUseCase";
import { PrismaClient } from "@prisma/client";
const router = express.Router();
const prisma = new PrismaClient({
errorFormat: "minimal",
});
/**
* ユーザー新規作成
*/
router.post("", async (req: Request, res: Response) => {
・・・
});
/**
* ユーザー一覧取得
*/
router.get("", async (req: Request, res: Response) => {
// T0D0: ユースケースに切り出す
const users = await prisma.user.findMany();
return res.status(200).json({
users,
});
});
/**
* ユーザー取得
*/
router.get("/:id", async (req: Request, res: Response) => {
const useCase = new FindUserUseCase();
const user = await useCase.find(req.params.id);
return res.status(200).json({
user,
});
});
/**
* ユーザー更新
*/
router.patch("", async (req: Request, res: Response) => {
// 後ほど実装
});
/**
* ユーザー削除
*/
router.delete("", async (req: Request, res: Response) => {
// 後ほど実装
});
export default router;
api/modules/user/useCase/FindUserUseCase.ts
import { User } from "@prisma/client";
import { UserRepositoryImpl } from "../repository/UserRepositoryImpl";
import { UserRepository } from "../repository/UserRepository";
export class FindUserUseCase {
private userRepository: UserRepository;
public constructor() {
// TODO: DI
this.userRepository = new UserRepositoryImpl();
}
public async find(id: string): Promise<User | null> {
return await this.userRepository.find(id);
}
}
api/modules/user/repository/UserRepository.ts
import { User } from "@prisma/client";
export interface UserRepository {
save(name: string, email: string): Promise<User>;
// 追加
find(id: string): Promise<User | null>;
}
api/modules/user/repository/UserRepositoryImpl.ts
import { PrismaClient, User } from "@prisma/client";
import { UserRepository } from "./UserRepository";
export class UserRepositoryImpl implements UserRepository {
async save(name: string, email: string): Promise<User> {
const prisma = new PrismaClient();
const user = await prisma.user.create({
data: {
email,
name,
},
});
return user;
}
async find(id: string): Promise<User | null> {
const prisma = new PrismaClient();
return await prisma.user.findUnique({
where: { id },
});
}
}
リクエスト検証
Thunder Clientを開き、GET、urlをhttp://localhost:18000/usersを指定。Sendをクリックし、右側のステータスが200で、作成済みユーザーが全件返ってきていればOK。
また、ID指定での1件取得も確認。GET、urlをhttp://localhost:18000/users/<userId>を指定。
Update
api/modules/user/routes/user.ts
import express, { Request, Response } from "express";
import { CreateUserUseCase } from "../useCase/CreateUserUseCase";
import { PrismaClient } from "@prisma/client";
import { FindUserUseCase } from "../useCase/FindUserUseCase";
const router = express.Router();
const prisma = new PrismaClient({
errorFormat: "minimal",
});
/**
* ユーザー新規作成
*/
router.post("", async (req: Request, res: Response) => {
・・・
});
/**
* ユーザー一覧取得
*/
router.get("", async (req: Request, res: Response) => {
・・・
});
/**
* ユーザー取得
*/
router.get("/:id", async (req: Request, res: Response) => {
・・・
});
/**
* ユーザー更新
*/
router.patch("", async (req: Request, res: Response) => {
const { id, email, name } = req.body.data;
// T0D0: ユースケースに切り出す
const user = await prisma.user.update({
where: {
id,
},
data: {
email,
name,
},
});
return res.status(200).json({
user,
});
});
/**
* ユーザー削除
*/
router.delete("", async (req: Request, res: Response) => {
// 後ほど実装
});
export default router;
// {
// "data" : {
// "name": "hoge",
// "email": "hoge@gmail.com"
// }
// }
リクエスト検証
Thunder Clientを開き、PATCH、urlをhttp://localhost:18000/usersを指定。Sendをクリックし、右側のステータスが200で、指定ユーザーの名前とメールアドレスが変わった状態で返ってきていればOK。
Delete
api/modules/user/routes/user.ts
import express, { Request, Response } from "express";
import { CreateUserUseCase } from "../useCase/CreateUserUseCase";
import { PrismaClient } from "@prisma/client";
import { FindUserUseCase } from "../useCase/FindUserUseCase";
const router = express.Router();
const prisma = new PrismaClient({
errorFormat: "minimal",
});
/**
* ユーザー新規作成
*/
router.post("", async (req: Request, res: Response) => {
・・・
});
/**
* ユーザー一覧取得
*/
router.get("", async (req: Request, res: Response) => {
・・・
});
/**
* ユーザー取得
*/
router.get("/:id", async (req: Request, res: Response) => {
・・・
});
/**
* ユーザー更新
*/
router.patch("", async (req: Request, res: Response) => {
・・・
});
/**
* ユーザー削除
*/
router.delete("", async (req: Request, res: Response) => {
const { id } = req.body;
// T0D0: ユースケースに切り出す
const user = await prisma.user.delete({
where: {
id,
},
});
return res.status(200).json({
user,
});
});
export default router;
リクエスト検証
Thunder Clientを開き、DELETE、urlをhttp://localhost:18000/usersを指定。Sendをクリックし、右側のステータスが200で、指定ユーザーが削除され、削除したユーザ情報が返ってきていればOK。
Part2はここまで。Part3ではフロントエンドのアプリケーションを作成し、APIと通信する。
まとめ
いかがでしたでしょうか。本記事は、ExpressとPrismaでAPIをサクッと作るシリーズのPart2です。Part2では主にPart1で作成したPrisma Clientを駆使してAPIの実装とリクエストの検証を行なっています。