はじめに
練習がてら、記事のタイトル通りの実装をした時のメモ
create-next-app
下記の設定で create-next-appしていく。
爆速のbun使っていく。
bunx create-next-app@latest
✔ What is your project named? … next-hono-prisma-ddd
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like your code inside a `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to use Turbopack for `next dev`? … No / Yes
✔ Would you like to customize the import alias (`@/*` by default)? … No / Yes
Creating a new Next.js app in /Users/hasunumatatsuya/next-hono-prisma-ddd.
Using bun.
Initializing project with template: app-tw
Installing dependencies:
- react
- react-dom
- next
Installing devDependencies:
- typescript
- @types/node
- @types/react
- @types/react-dom
- @tailwindcss/postcss
- tailwindcss
- eslint
- eslint-config-next
- @eslint/eslintrc
bun install v1.2.4 (fd9a5ea6)
+ @eslint/eslintrc@3.3.0
+ @tailwindcss/postcss@4.0.14
+ @types/node@20.17.24 (v22.13.10 available)
+ @types/react@19.0.10
+ @types/react-dom@19.0.4
+ eslint@9.22.0
+ eslint-config-next@15.2.2
+ tailwindcss@4.0.14
+ typescript@5.8.2
+ next@15.2.2
+ react@19.0.0
+ react-dom@19.0.0
311 packages installed [14.71s]
Initialized a git repository.
Success! Created next-hono-prisma-ddd at /Users/hasunumatatsuya/next-hono-prisma-ddd
Auth.jsを使う前提のDBを作る
Auth.jsの部分
まずは下記のサイトを参考にAuth.jsのセットアップをしていく。
bun add next-auth@beta
bunx auth secret
何となくGithubとGoogleを選択。
.envとかは下記のリンクを参考にしてください。
auth.tsでは、useSessionでuserIdを取得できる様にしておく。
import { prisma } from "@/repository/prisma";
import { PrismaAdapter } from "@auth/prisma-adapter";
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [GitHub, Google],
callbacks: {
async session({ session, user }) {
if (session.user) {
session.user.id = user.id;
}
return session;
},
},
});
import { handlers } from "@/app/auth";
export const { GET, POST } = handlers;
export { auth as middleware } from "@/app/auth";
AUTH_GITHUB_ID=**
AUTH_GITHUB_SECRET=**
AUTH_GOOGLE_ID=**
AUTH_GOOGLE_SECRET=**
DB(prisma)の部分
まずはPrismaをセットアップしていく。
bun add prisma typescript tsx @types/node --save-dev
bunx prisma init
これで一旦完了。
続いて、Auth.jsのチュートリアル通りにスキーマを設定していく。参考は以下。
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([provider, providerAccountId])
}
model Session {
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model VerificationToken {
identifier String
token String
expires DateTime
@@id([identifier, token])
}
// Optional for WebAuthn support
model Authenticator {
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([userId, credentialID])
}
dockerで環境構築
app,db,prisma studioを用意する。
database周りの変数を追加する。
DATABASE_URL=**
POSTGRES_USER=**
POSTGRES_PASSWORD=**
POSTGRES_DB=**
AUTH_GITHUB_ID=**
AUTH_GITHUB_SECRET=**
AUTH_GOOGLE_ID=**
AUTH_GOOGLE_SECRET=**
services:
app:
build:
context: .
dockerfile: ./docker/Dockerfile
container_name: nextjs14-container
environment:
WATCHPACK_POLLING: true
DATABASE_URL: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public"
AUTH_GITHUB_ID: ${AUTH_GITHUB_ID}
AUTH_GITHUB_SECRET: ${AUTH_GITHUB_SECRET}
AUTH_GOOGLE_ID: ${AUTH_GOOGLE_ID}
AUTH_GOOGLE_SECRET: ${AUTH_GOOGLE_SECRET}
ports:
- "3000:3000"
volumes:
- /app/node_modules
- .:/app
depends_on:
- db
studio:
build:
context: .
dockerfile: ./docker/Dockerfile
container_name: nextjs14-studio
environment:
DATABASE_URL: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public"
depends_on:
- db
ports:
- "5555:5555"
volumes:
- /app/node_modules
- .:/app
command: >
bunx prisma studio
db:
image: postgres:15-alpine
container_name: nextjs14-db
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: always
volumes:
postgres_data:
up:
docker compose -f ./docker-compose.yml up -d
down:
docker compose -f ./docker-compose.yml down
build:
docker compose up --build -d
FROM oven/bun:latest
WORKDIR /app
COPY ./docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
COPY package*.json ./
RUN bun install
COPY . .
CMD ["/usr/local/bin/entrypoint.sh"]
#!/bin/sh
set -e
echo "Running Prisma migrations..."
bunx prisma generate
bunx prisma db push
echo "Starting Next.js application..."
# アプリケーションの起動(必要に応じて開発モードや本番モードに切り替え)
bun run dev
make buildで立ち上げ
make build
http://localhost:5555 と http://localhost:3000 で起動を確認
http://localhost:3000 でログインをすると、userテーブルにデータが入っていることが確認できます
hono RPC
hono RPC をセットアップしていく。
bun add hono
user一覧を取得する想定でレイヤードアーキテクチャを構築していく。
useSWRのためのfetcher↓
export async function fetcher<T>(
requestFn: () => Promise<Response>
): Promise<T> {
const res = await requestFn();
if (!res.ok) {
throw new Error(`Request failed with status ${res.status}`);
}
return (await res.json()) as T;
}
user一覧を取得するた目の自作hooks
import useSWR from "swr";
import { User } from "@prisma/client";
import { fetcher } from "@/app/_utils/request/fetcher";
import { client } from "@/app/_utils/request/rpc";
export function useUsers() {
const { data, error, isLoading } = useSWR<User[]>("api/user/", () =>
fetcher<User[]>(() => client.api.user.users.$get())
);
return {
data,
error,
isLoading,
};
}
import { Hono } from "hono";
import userController from "@/controller/userController";
import { handle } from "hono/vercel";
const app = new Hono().basePath("/api");
export const routes = app.route("/user", userController);
export const GET = handle(app);
export const POST = handle(app);
export type AppType = typeof routes;
フロントからのリクエストを受け取るcontroller層
import { userUsecase } from "@/usecase/useUsecase";
import { Hono } from "hono";
const app = new Hono().get("/users", async (c) => {
const users = await userUsecase.users();
return c.json(users, 200);
});
export default app;
ビジネスロジックを組み立てるusecase層。
controller層とusecase層は各ドメインに対して1対1とする。
import { userRepository } from "@/repository/userRepository";
export const userUsecase = {
users: async () => {
const user = await userRepository.users();
return user;
},
};
DBからデータを取得するrepository層
import { prisma } from "./prisma";
export const userRepository = {
users: async () => {
return await prisma.user.findMany();
},
};
フロント周りを調整する。
まずはログイン、ログアウト用のモジュールを作る。
import { signIn } from "next-auth/react";
export const userSignInWithProvider = async (provider: string) => {
await signIn(provider);
};
import { signOut } from "next-auth/react";
export const userSignOut = async () => {
await signOut();
};
"use client";
import { useSession } from "next-auth/react";
import { useUsers } from "./_features/user/hooks/useUsers";
import { userSignInWithProvider } from "./_features/user/actions/login";
import { userSignOut } from "./_features/user/actions/logout";
export default function Home() {
const { data: users } = useUsers();
const { data: session } = useSession();
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<div>{users ? users.map((u) => u.name) : "Loading..."} </div>
<button type="button" onClick={() => userSignInWithProvider("github")}>
Sign in with GitHub
</button>
<button type="button" onClick={() => userSignInWithProvider("google")}>
Signin with Google
</button>
<button onClick={userSignOut}>Sign Out</button>
<div>{session?.user?.name}</div>
<div>{session?.user?.id}</div>
</div>
);
}
以上で雛形は完成です。
makeb uild
の後、localhost:3000を開き、GithubやGoogleでログインすると、user一覧のデータ、ログインユーザのnameやidが取得できているのが確認できます。
終わりに
今回は、docker環境を用いて Next.js, honoRPC, prisma + postgreSQLを用いて雛形を作成した。
本当はレイヤードアーキテクチャではなくDDDを使いたかったのでが勉強中のため雑なレイヤードアーキテクチャにしてしまっています
ファイルの置く場所が書いていない部分は迷っている部分なので個人で決めていただければと思います