ãã«ã¹ã¿ãã¯Webã¢ããªã±ãŒã·ã§ã³ã®ãã€ã©ãŒãã¬ãŒãã§ãã
| ã¬ã€ã€ãŒ | æè¡ |
|---|---|
| ãªããŒã¹ãããã· | nginx 1.25 |
| ããã³ããšã³ã | Next.js 14 (App Router) + TypeScript |
| ããã¯ãšã³ã | FastAPI + Python 3.12 |
| ããŒã¿ããŒã¹ | MongoDB 7 + Beanie (ODM) |
| èªèšŒ | Keycloak 25 (OpenID Connect / SSO) |
| å®è¡ç°å¢ | Docker + docker-compose |
boilerplate/
âââ docker-compose.yml # éçºçšïŒããã©ã«ãïŒ
âââ docker-compose.prod.yml # æ¬çªçš
âââ .env.example
âââ .gitignore
âââ nginx/
â âââ nginx.conf # ãªããŒã¹ãããã·èšå®
âââ frontend/
â âââ Dockerfile # éçºçšïŒnext dev / inspector æå¹ïŒ
â âââ Dockerfile.prod # æ¬çªçšïŒãã«ãã¹ããŒãžïŒ
â âââ package.json
â âââ next.config.mjs
â âââ src/
â âââ app/
â â âââ layout.tsx # ã«ãŒãã¬ã€ã¢ãŠã
â â âââ page.tsx # ã©ã³ãã£ã³ã°ããŒãžïŒå
¬éïŒ
â â âââ globals.css # ããŒã¹ã¹ã¿ã€ã«ïŒCSS ã¬ã€ã€ãŒïŒ
â â âââ dashboard/
â â â âââ page.tsx # ããã·ã¥ããŒãæŠèŠïŒèŠèªèšŒïŒ
â â â âââ rest-api/page.tsx # REST API ãã¢
â â â âââ websocket/page.tsx # WebSocket ãã¢
â â â âââ sse/page.tsx # SSE ãã¢
â â âââ api/auth/[...nextauth]/route.ts
â âââ components/
â â âââ Providers.tsx # SessionProvider ã©ãããŒ
â â âââ NavBar.tsx # ããã²ãŒã·ã§ã³ïŒãµãã¿ãä»ãïŒ
â â âââ RestApiDemo.tsx # REST CRUD ãã¢ã³ã³ããŒãã³ã
â â âââ WebSocketDemo.tsx # WebSocket ãã£ãããã¢ã³ã³ããŒãã³ã
â â âââ SseDemo.tsx # SSE 鲿ãã¢ã³ã³ããŒãã³ã
â âââ lib/
â â âââ auth.ts # NextAuth + Keycloak èšå®
â â âââ fetch.ts # fetchWithRetry ãŠãŒãã£ãªãã£
â âââ types/
â âââ next-auth.d.ts # Session åæ¡åŒµ
âââ backend/
â âââ Dockerfile
â âââ entrypoint.sh # æ¬çªçšïŒ--reload ãªãã»debugpy ãªãïŒ
â âââ entrypoint.dev.sh # éçºçšïŒ--reload ããã»debugpy :5678ïŒ
â âââ requirements.txt
â âââ requirements-dev.txt # pytest / debugpyïŒéçºå°çšïŒ
â âââ main.py # FastAPI ãšã³ããªãŒãã€ã³ã
â âââ database.py # Motor ã¯ã©ã€ã¢ã³ã / èšå®
â âââ auth.py # Keycloak JWKS ã«ããJWTæ€èšŒ
â âââ models/
â â âââ item.py # Beanie Document ã¢ãã«
â âââ routers/
â âââ items.py # REST CRUD ãšã³ããã€ã³ã
â âââ ws.py # WebSocket ãšã³ããã€ã³ã
â âââ sse.py # SSE ãšã³ããã€ã³ã
âââ keycloak/
âââ realm-export.json # ã¬ã«ã ã»ãŠãŒã¶ãŒåæèšå®ïŒèªåã€ã³ããŒãïŒ
âââ scripts/
âââ create-users.sh # Admin REST API ã§ãŠãŒã¶ãŒã远å ããã¹ã¯ãªãã
cp .env.example .env
# éçºç°å¢ã¯ããã©ã«ãå€ã®ãŸãŸã§åäœããŸãdocker compose up --builddocker-compose.ymlïŒéçºçšïŒã䜿çšããŸãã以äžã®æ©èœãæå¹ã§ãïŒ
- backend:
entrypoint.dev.shã§èµ·åïŒdebugpy :5678ã»uvicorn--reloadïŒ - frontend:
next devïŒHMRã»Node.js inspector :9229ïŒ - ã³ãŒãããŠã³ã: ãã¹ãã®ãã¡ã€ã«å€æŽãã³ã³ããã«å³æåæ
- ãããã°: VSCode ãããFull Stack: attach to Docker containersãã§ã¢ã¿ããå¯èœ
docker compose -f docker-compose.prod.yml up --builddocker-compose.prod.ymlïŒæ¬çªçšïŒã䜿çšããŸãïŒ
- backend:
entrypoint.shã§èµ·åïŒ--reloadãªãã»debugpy ãªãïŒ - frontend:
next build â next startïŒãã«ãã¹ããŒãžãã«ãïŒ - ã³ãŒãããŠã³ããªã: ã³ãŒãã¯ã€ã¡ãŒãžã«çŒã蟌ãŸãã
æ¬çªç°å¢ãšããŠå ¬éããåã«ãæ¬çªç°å¢ã«åãã TODOããå¿ ã確èªããŠãã ããã
| é ç® | éçº | æ¬çª |
|---|---|---|
| èµ·åã³ãã³ã | docker compose up --build |
docker compose -f docker-compose.prod.yml up --build |
| backend entrypoint | entrypoint.dev.sh |
entrypoint.sh |
uvicorn --reload |
â ãã | â ãªã |
| debugpyïŒ:5678ïŒ | â ãã | â ãªã |
| Node.js inspectorïŒ:9229ïŒ | â ãã | â ãªã |
| ã³ãŒãããŠã³ã | â ãã¹ãâã³ã³ãã | â ã€ã¡ãŒãžã«çŒã蟌㿠|
| requirements-dev.txt | â ã€ã³ã¹ããŒã« | â ã¹ããã |
| frontend Dockerfile | Dockerfile |
Dockerfile.prod |
ååèµ·åæã¯ Keycloak ã®ããŒã¿ããŒã¹åæåã« 1ã2å ããããŸãã
åèµ·åæã®æ³šæ
keycloak/realm-export.jsonã docker-compose ã® Keycloak èšå®ã倿Žããå Žåã¯ã æ¢åã®ããªã¥ãŒã ãåé€ããŠããåèµ·åããŠãã ããã# éçºç°å¢ docker compose down -v && docker compose up --build # æ¬çªç°å¢ docker compose -f docker-compose.prod.yml down -v docker compose -f docker-compose.prod.yml up --build
| ãµãŒãã¹ | URL | åè |
|---|---|---|
| ã¢ããªïŒnginx çµç±ïŒ | http://localhost | ã¡ã€ã³ãšã³ããªãŒãã€ã³ã |
| ããã³ããšã³ãïŒçŽæ¥ïŒ | http://localhost:3000 | ãããã°çš |
| FastAPI Swagger UI | http://localhost:8000/docs | ãããã°çš |
| Keycloak 管çã³ã³ãœãŒã« | http://localhost:8080 | ãŠãŒã¶ãŒå: admin / ãã¹ã¯ãŒã: admin |
keycloak/realm-export.json ã§ä»¥äžã®ãŠãŒã¶ãŒãèµ·åæã«èªåäœæãããŸãã
| ãŠãŒã¶ãŒå | ã¡ãŒã« | ãã¹ã¯ãŒã | ããŒã« | çšé |
|---|---|---|---|---|
admin-user |
admin@example.com | password |
user + admin | 管çè æš©éã®åäœç¢ºèª |
normal-user |
user@example.com | password |
user | äžè¬ãŠãŒã¶ãŒã®åäœç¢ºèª |
disabled-user |
disabled@example.com | password |
user | ç¡å¹ãŠãŒã¶ãŒã®æå確èªïŒãã°ã€ã³äžå¯ïŒ |
ã»ãã¥ãªãã£äžã®æ³šæ
realm-export.jsonã«ã¯ãã¹ã¯ãŒããå¹³æã§å«ãŸããŸãã ãããªãã¯ãªããžããªãžã®å ¬éãã¹ããŒãžã³ã°ã»æ¬çªç°å¢ã§ã®äœ¿çšã¯é¿ããŠãã ããã
MongoDB ã¯ã¹ããŒãã¬ã¹ã®ããããã€ã°ã¬ãŒã·ã§ã³ã¯äžèŠã§ãã
ã€ã³ããã¯ã¹ã¯ backend/models/ ã®å Document ã¯ã©ã¹ã® Settings.indexes ã§å®£èšãã
ã¢ããªèµ·åæã® init_beanie() ã§èªåäœæãããŸãã
# backend/models/item.py ã®äŸ
class Item(Document):
owner_id: str
class Settings:
name = "items"
indexes = [
"owner_id", # åäžãã£ãŒã«ãã€ã³ããã¯ã¹
[("owner_id", 1), ("created_at", -1)], # è€åã€ã³ããã¯ã¹
]æ°ããã¢ãã«ã远å ãããšãã®æé
backend/models/æ°ã¢ãã«.pyãäœæïŒDocumentãç¶æ¿ïŒbackend/main.pyã®init_beanie()ã®document_modelsã«è¿œådocker compose restart backendã§ã€ã³ããã¯ã¹ãèªåäœæããã
ãã¹ãã¯ããã¯ãšã³ãã³ã³ããå
ã§å®è¡ããŸããMongoDB ãå¿
èŠãªãããdocker compose up ã§ã³ã³ãããèµ·åããŠããå®è¡ããŠãã ããã
# å
šãã¹ã
docker compose exec backend pytest
# slow ããŒã«ãŒãé€ããé«éå®è¡ïŒçŽ4ç§ããã SSE ã¹ããªãŒã ãã¹ããé€ãïŒ
docker compose exec backend pytest -m "not slow"
# ã«ãã¬ããžä»ã
docker compose exec backend pytest --cov=. --cov-report=term-missing| ãã¡ã€ã« | å 容 |
|---|---|
tests/test_items.py |
CRUD å šãã¿ãŒã³ïŒæ£åžžç³»ã»404ã»422ã»403ã»ä»ãŠãŒã¶ãŒè¶æš©ïŒ |
tests/test_sse.py |
SSE ãšã³ããã€ã³ãïŒtask_idçºè¡ã»404ã»content-typeïŒ |
tests/test_migrations.py |
ãã€ã°ã¬ãŒã·ã§ã³ãã¡ã€ã«ã®éçãã§ãã¯ïŒDB倿ŽãªãïŒ |
å€ãŠãŒã¶ãŒãã¹ãã®æ¹é: ä»ãŠãŒã¶ãŒã®ããŒã¿ã¯ HTTP çµç±ã§ã¯ãªã SQLAlchemy ã§çŽæ¥ DB ã«æ¿å
¥ããŸãïŒconftest.py ã® db_session ãã£ã¯ã¹ãã£ã䜿çšïŒã
ããã³ããšã³ãã®åäœãã¹ã㯠ããŒã«ã« ã§å®è¡ããŸãïŒDocker äžèŠïŒã
cd frontend
npm install # ååã®ã¿
npm test # å
šãã¹ãïŒwatch ãªãïŒ
npm run test:watch # ãã¡ã€ã«å€æŽãæ€ç¥ããŠèªååå®è¡
npm run test:coverage # ã«ãã¬ããžä»ãïŒ80% ãç®æšïŒ| ãã¡ã€ã« | 察象 | ãã¹ãæ° |
|---|---|---|
src/lib/__tests__/fetch.test.ts |
fetchWithRetry ã®å
šãã¿ãŒã³ |
çŽ30ä»¶ |
ãã¹ãèšèšã®ãã€ã³ã:
global.fetchãjest.fn()ã§ã¢ãã¯ïŒå®éã®ãããã¯ãŒã¯éä¿¡ãªãïŒbaseDelay: 0, jitter: 0ãæž¡ããŠåŸ æ©æéãŒãã§é«éå®è¡jest-environment-nodeïŒDOM äžèŠã®çŽç²é¢æ°ã«ã€ã jsdom ããé«éïŒ
ãã¹ãã±ãŒã¹äžèЧ:
| ã°ã«ãŒã | å 容 |
|---|---|
| æ£åžžç³» | 1åç®æåã»options ã®ééã»ã¬ã¹ãã³ã¹ã®è¿åŽ |
| 5xx ãªãã©ã€ | 500/502/503/504 ã§ãªãã©ã€ã»maxRetries åŸã«ãšã©ãŒ |
| 4xx éãªãã©ã€ | 400/401/403/404/422/429 ã¯å³ã¹ããŒã»1åã®ã¿åŒã°ãã |
| ãããã¯ãŒã¯ãšã©ãŒ | TypeError / DNS ãšã©ãŒã§ãªãã©ã€ |
| onRetry ã³ãŒã«ãã㯠| attemptçªå·ã»maxRetriesã»Error ã®æ€èšŒ |
| ãªãã·ã§ã³ | maxRetries ã«ã¹ã¿ã èšå® |
DB åé¢ïŒããã¯ãšã³ãïŒ: åãã¹ãã¯ãã¹ãçš DBïŒappdb_testïŒã§å®è¡ããããã¹ãåŸã«ã³ã¬ã¯ã·ã§ã³ã drop ããããšã§ç¶æ
ããªã»ããããŸãããã¹ãéã®ç¶æ
æ±æã¯ãããŸããã
Phase 3 ã¯ããŒã éçºã«ãã㊠Phase 1ã»2 ãèªååãããã®ã§ãããã®boilerplateã§ã¯å«ããŠããŸããããå®éã®ãããžã§ã¯ãã«ç§»è¡ããéã«è¿œå ããããšãæšå¥šããŸãã
æŠèŠ: ããã·ã¥ã»ãã«ãªã¯ãšã¹ãæã« Phase 1ã»2 ã®ãã¹ããèªåå®è¡ããŸãã
å®è£
ããå Žåã®æ§æäŸïŒ.github/workflows/test.ymlïŒ:
name: Tests
on: [push, pull_request]
jobs:
# ââ ããã¯ãšã³ãïŒpytestïŒââââââââââââââââââââââââââââââââ
backend:
runs-on: ubuntu-latest
services:
mongo:
image: mongo:7
ports: ["27017:27017"]
options: >-
--health-cmd "mongosh --eval \"db.adminCommand('ping')\""
--health-interval 5s
--health-retries 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -r requirements.txt -r requirements-dev.txt
working-directory: backend
- run: pytest -m "not slow" --cov=. --cov-report=xml
working-directory: backend
env:
MONGODB_URL: mongodb://localhost:27017
# ââ ããã³ããšã³ãïŒJestïŒââââââââââââââââââââââââââââââ
frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20" }
- run: npm ci
working-directory: frontend
- run: npm test
working-directory: frontendæ€èšãã€ã³ã:
slowããŒã«ãŒã® SSE ã¹ããªãŒã ãã¹ã㯠CI ã§ã¯é€å€ãã宿å®è¡ïŒscheduleïŒã§å®æœãããšãã- ã«ãã¬ããžã¬ããŒã㯠Codecov çãšé£æºããããšã§ PR äžã«å·®å衚瀺ã§ãã
- Keycloak ã CI ã«å«ãããšèµ·åã«2å以äžããããããèªèšŒã®ãã¹ã㯠JWT æ€èšŒã®ã¢ãã¯ã§ä»£æ¿ãã
Phase 4 ã¯ãŠãŒã¶ãŒã®å®éã®æäœãããŒããã©ãŠã¶ãä»ããŠãã¹ããããã®ã§ããèšå®ã³ã¹ããé«ãããããã®boilerplateã§ã¯å«ããŠããŸããããéèŠãªãŠãŒã¶ãŒãããŒãä¿è·ãããå Žåã«è¿œå ããŸãã
察象ãããŒïŒæå°éã®æšå¥šã»ããïŒ:
| ãã¹ã | å 容 | åªå 床 |
|---|---|---|
| ãã°ã€ã³ãã㌠| ãããããŒãž â Keycloak â ããã·ã¥ããŒããžã®ãªãã€ã¬ã¯ã | é« |
| ã¢ã€ãã äœæ | ãã©ãŒã å ¥å â éä¿¡ â äžèЧã«è¡šç€ºããã | é« |
| æªèªèšŒãªãã€ã¬ã¯ã | /dashboard ã«çŽæ¥ã¢ã¯ã»ã¹ â ãããããŒãžãžãªãã€ã¬ã¯ã |
äž |
| WebSocket çé | æ¥ç¶ â ã¡ãã»ãŒãžéä¿¡ â å信衚瀺 | äœ |
å®è£ ããå Žåã®æ§æäŸ:
e2e/
âââ playwright.config.ts # ããŒã¹URLã»ãã©ãŠã¶èšå®
âââ fixtures/
â âââ auth.ts # ãã°ã€ã³æžã¿ããŒãžã®ãã£ã¯ã¹ãã£
âââ tests/
âââ auth.spec.ts # ãã°ã€ã³ãããŒ
âââ items.spec.ts # ã¢ã€ãã CRUD
Keycloak ãã°ã€ã³ã®æ±ã:
Keycloak ã®ãã°ã€ã³ãã©ãŒã ã Playwright ã§æäœããã«ã¯ page.fill() ã§ãŠãŒã¶ãŒåã»ãã¹ã¯ãŒããå
¥åããæ¹æ³ã䜿ããŸãããã ã以äžã®ç¹ã«æ³šæãå¿
èŠã§ãã
// e2e/fixtures/auth.ts
export async function loginAsTestUser(page: Page) {
await page.goto('http://localhost')
await page.click('button:has-text("Sign in")')
// Keycloak ã®ãã°ã€ã³ãã©ãŒã
await page.fill('#username', 'normal-user')
await page.fill('#password', 'password')
await page.click('[type="submit"]')
await page.waitForURL('**/dashboard/**')
}æ€èšãã€ã³ã:
- E2E ã¯ãã¹ãŠã®ãµãŒãã¹ãèµ·åããç¶æ
ïŒ
docker compose upïŒã§å®è¡ãããã CI æéãå€§å¹ ã«äŒžã³ããmainãã©ã³ããžã®ããŒãžæã®ã¿å®è¡ããèšå®ãçŸå®ç - Keycloak ã®èµ·åã«æéããããããã
wait-onãdocker compose waitã§èµ·å確èªãæã - ãã¹ãããŒã¿ã¯æ¯å Keycloak ã®
create-users.shãš MongoDB ã³ã¬ã¯ã·ã§ã³ã®dropã§ãªã»ãããã - ãã©ãŠã¶ã®èšèªèšå®ãç»é¢ãµã€ãºã Keycloak ã® UI ã«åœ±é¿ããããšãããïŒ
playwright.config.tsã§åºå®ããïŒ
nginx ãã·ã³ã°ã«ãšã³ããªãŒãã€ã³ããšããŠå šãã©ãã£ãã¯ãæ¯ãåããŸãã
ãã©ãŠã¶ :80
â
ââ /ws/ â backend:8000 (WebSocket)
ââ /api/sse/ â backend:8000 (SSE â ãããã¡ãªã³ã°ç¡å¹)
ââ /api/auth/ â frontend:3000 (NextAuth ã³ãŒã«ããã¯)
ââ /api/ â backend:8000 (FastAPI REST)
ââ /_next/static/ â frontend:3000 (immutable ãã£ãã·ã¥ä»ã)
ââ /_next/webpack-hmr â frontend:3000 (éçºçš HMR WebSocket)
ââ / â frontend:3000 (ãã®ä»ãã¹ãŠ)
/_next/static/ é
äžã®ãã¡ã€ã«ã¯ãã«ãæã«ã³ã³ãã³ãããã·ã¥ããã¡ã€ã«åã«ä»äžãããããã
nginx ã Cache-Control: max-age=31536000, immutable ãèšå®ããŠãå®å
šã§ãã
nginx ã§ä»¥äžã®ããããŒãå šã¬ã¹ãã³ã¹ã«ä»äžããŠããŸãã
| ããã㌠| èšå®å€ | 广 |
|---|---|---|
X-Frame-Options |
DENY |
ã¯ãªãã¯ãžã£ããã³ã°é²æ¢ |
X-Content-Type-Options |
nosniff |
MIME ã¹ãããã£ã³ã°é²æ¢ |
Referrer-Policy |
strict-origin-when-cross-origin |
ãªãã¡ã©ãŒæ å ±ã®å¶åŸ¡ |
Permissions-Policy |
ã«ã¡ã©çãç¡å¹å | äžèŠãªãã©ãŠã¶æ©èœãå¶é |
Content-Security-Policy |
default-src 'self' ããŒã¹ |
XSSã»ã€ã³ãžã§ã¯ã·ã§ã³å¯Ÿç |
æ¬çªç°å¢ã§ã®æ³šæ CSP ã®
unsafe-inline/unsafe-eval㯠Next.js ã®éçºã¢ãŒãïŒHMRïŒã«å¿ èŠã§ããã æ¬çªãã«ãã§ã¯ nonce ããŒã¹ã«å€æŽããŠåé€ããŠãã ãããStrict-Transport-Security㯠HTTPS ç°å¢ã§ã®ã¿æå¹åããŠãã ããã
Chapter 4ãRetry Strategies and Backoffãã®å®è£
ã§ããå
š API åŒã³åºãã¯çŽ ã® fetch ã®ä»£ããã« fetchWithRetry ã䜿çšããŠããŸãã
ãããã¯ãŒã¯ãšã©ãŒã»5xx â ææ°ããã¯ãªãã§ãªãã©ã€ïŒæå€§3åïŒ
4xx ã¯ã©ã€ã¢ã³ããšã©ãŒ â ãªãã©ã€ããªãïŒãªã¯ãšã¹ãåŽã®åé¡ïŒ
ããã¯ãªãã¹ã±ãžã¥ãŒã«ïŒãžãã¿ãŒä»ãïŒ
| è©Šè¡ | åŸ æ©æé |
|---|---|
| 1åç®å€±æ | 1000ms ± ã©ã³ãã ãžãã¿ãŒ |
| 2åç®å€±æ | 2000ms ± ã©ã³ãã ãžãã¿ãŒ |
| 3åç®å€±æ | 4000ms ± ã©ã³ãã ãžãã¿ãŒ |
ãžãã¿ãŒãå ããããšã§ãè€æ°ã¯ã©ã€ã¢ã³ããåæã«ãªãã©ã€ããéã«ãµãŒããŒãžã®ãªã¯ãšã¹ãã忣ãããŸãïŒthundering herd åé¡ã®åé¿ïŒã
/api/items/ ã«èªèšŒä»ãã® CRUD ãšã³ããã€ã³ãããããŸãã
GET /api/items/ ãã°ã€ã³ãŠãŒã¶ãŒã®ã¢ã€ãã äžèЧ
POST /api/items/ ã¢ã€ãã äœæ (201 Created)
GET /api/items/{id} ã¢ã€ãã ååŸ
DELETE /api/items/{id} ã¢ã€ãã åé€ (204 No Content)
- ãã¹ãŠã®ãšã³ããã€ã³ãã¯
Authorization: Bearer <token>ãèŠæ±ããŸãã - Keycloak ã® JWKS ãšã³ããã€ã³ãã§ããŒã¯ã³ãæ€èšŒããŸãã
- åãŠãŒã¶ãŒã¯èªåã®ã¢ã€ãã ã®ã¿åç §ã»æäœã§ããŸãã
- æ¥ç¶ããå šã¯ã©ã€ã¢ã³ããžã®ãããŒããã£ã¹ããã£ããã§ãã
- JSON ãã©ãŒããã:
{ "message": "Hello" } - ãã©ãŠã¶ãè€æ°ã¿ãã§éããŠåäœã確èªã§ããŸãã
Server-Sent Events ã«ããäžæ¹åãªã¢ã«ã¿ã€ã éä¿¡ã®ãµã³ãã«ã§ãã
POST /api/sse/tasks ã¿ã¹ã¯éå§ïŒBearer èªèšŒïŒâ task_id ãè¿ã
GET /api/sse/tasks/{task_id} EventSource ã§é²æã€ãã³ãã賌èª
EventSource 㯠Authorization ããããŒãéããªãããã
POST ã§ã¿ã¹ã¯ãéå§ã㊠task_id ãååŸãããã® task_id ã
URL ã«å«ããããšã§èªèšŒã代æ¿ããŠããŸãïŒChapter 4 ã® SSE ãã¿ãŒã³æºæ ïŒã
| SSE | WebSocket | |
|---|---|---|
| éä¿¡æ¹å | ãµãŒããŒâã¯ã©ã€ã¢ã³ãïŒäžæ¹åïŒ | åæ¹å |
| åããŠããçšé | 鲿éç¥ã»ãã°ã»AIå¿ç | ãã£ããã»ã²ãŒã |
| èªååæ¥ç¶ | ãã©ãŠã¶æšæºã§å¯Ÿå¿ | å®è£ ãå¿ èŠ |
keycloak/realm-export.jsonãèµ·åæã«èªåã€ã³ããŒããããŸãã- Next.js ã¯
next-auth+KeycloakProviderã§èªèšŒããŸãã - FastAPI 㯠Keycloak ã® JWKS ã䜿ã£ãŠ Bearer ããŒã¯ã³ãæ€èšŒããŸãã
Docker ç°å¢ã§ã¯ Next.js ã³ã³ãããšãã©ãŠã¶ãç°ãªããããã¯ãŒã¯ã«ããããã Keycloak ãžã®æ¥ç¶ã« 2 çš®é¡ã® URL ã䜿ãåããŠããŸãã
| ç°å¢å€æ° | å€ | çšé |
|---|---|---|
KEYCLOAK_ISSUER |
http://localhost:8080/realms/myrealm |
ããŒã¯ã³ã® iss æ€èšŒã»ãã©ãŠã¶èªèšŒãªãã€ã¬ã¯ãå
|
KEYCLOAK_INTERNAL_URL |
http://keycloak:8080/realms/myrealm |
ãµãŒããŒãµã€ãã® API åŒã³åºãïŒã³ã³ããå éšïŒ |
KC_HOSTNAME_URL |
http://localhost:8080 |
Keycloak ãçæããå
š URL ã®ããŒã¹ïŒiss ã¯ã¬ãŒã ã«åæ ïŒ |
KC_HOSTNAME_URL ãèšå®ããããšã§ Keycloak ãè¿ããã¹ãŠã® URLïŒããŒã¯ã³ã® issã
Discovery Document ã®åãšã³ããã€ã³ãïŒã localhost:8080 ããŒã¹ã«ãªãã
ãã©ãŠã¶ããã®èªèšŒãããŒãæ£åžžã«åäœããŸãã
/dashboard é
äžã®ããŒãžã¯ããµãŒããŒãµã€ããšã¯ã©ã€ã¢ã³ããµã€ãã®äž¡æ¹ã§ã»ãã·ã§ã³ãæ€èšŒããŠããŸãã
| ã¿ã€ãã³ã° | å®è£ ç®æ | 察象ã±ãŒã¹ |
|---|---|---|
| ããŒãžååããŒãæ | dashboard/*/page.tsxïŒãµãŒããŒã³ã³ããŒãã³ãïŒã® getServerSession |
æªãã°ã€ã³ã§ã®çŽæ¥ã¢ã¯ã»ã¹ |
| ããŒãžè¡šç€ºäžã®ã»ãã·ã§ã³å€±å¹ | NavBar.tsx ã® useEffectïŒã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ãïŒ |
JWT æéåãã»å¥ã¿ãããã®ãã°ã¢ãŠã |
ããŒãžããŒãæ
ââ getServerSession â ã»ãã·ã§ã³ãªã â redirect('/') ïŒãµãŒããŒãµã€ãïŒ
ããŒãžè¡šç€ºäž
ââ useSession ã® status ã 'unauthenticated' ã«å€å
ââ pathname ã /dashboard/* ãªãã° router.push('/') ïŒNavBar.tsxïŒ
ãªã NavBar ã«å®è£
ããã®ã: NavBar ã¯ãã¹ãŠã®ããŒãžã§æç»ãããã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ãã§ããã§ã« useSession ãš usePathname ãä¿æããŠããŸããããã§äžå
管çããããšã§ãæ°ãããã¢ããŒãžã远å ããŠãèªåçã«åãä¿è·ãé©çšãããŸãã
èµ·ååŸã« Admin REST API çµç±ã§ãŠãŒã¶ãŒã远å ãããå Žå:
docker compose exec keycloak bash /opt/keycloak/data/import/scripts/create-users.shkeycloak/scripts/create-users.sh å
ã® create_user è¡ã远èšããã ãã§
ä»»æã®ãŠãŒã¶ãŒã远å ã§ããŸããæ¢åãŠãŒã¶ãŒã¯ã¹ããããããããåªçã«å®è¡ã§ããŸãã
docker-compose.prod.yml ã¯æ¬çªæ§æã®åºçºç¹ã§ãããå
¬éåã«ä»¥äžã察å¿ããŠãã ããã
-
NEXTAUTH_SECRETã匷åãªã©ã³ãã å€ã«å€æŽããïŒopenssl rand -base64 32ïŒ -
KEYCLOAK_ADMIN_PASSWORDã倿Žãã -
KEYCLOAK_CLIENT_SECRETã倿Žãã - DB ãã¹ã¯ãŒãïŒ
POSTGRES_PASSWORDïŒã倿Žãã -
KC_HOSTNAME_URLãæ¬çªãã¡ã€ã³ã«å€æŽããïŒäŸ:https://example.comïŒ -
KEYCLOAK_ISSUER/NEXT_PUBLIC_API_URLãæ¬çªãã¡ã€ã³ã«åããã -
nginx/nginx.confã« TLS èšå®ïŒlisten 443 ssl http2+ssl_certificateïŒã远å ãã -
docker-compose.prod.ymlã§ frontendã»backendã»keycloak ã®ports:ãåé€ã nginx ã®ã¿å ¬éãã - Keycloak ã®
command: start-devãstartã«å€æŽããïŒæ¬çªã¢ãŒãïŒ - Keycloak ã®
sslRequiredã"external"ã«å€æŽãã - æ¬çªã® MongoDB ã¯èªèšŒä»ã URI ã䜿çšããïŒäŸ:
mongodb://user:pass@mongo:27017/appdbïŒ - æ¬çªã§ã¯ MongoDB ã¬ããªã«ã»ããæ§æãæ€èšããïŒãã«ãããã¥ã¡ã³ããã©ã³ã¶ã¯ã·ã§ã³ã»èªã¿åãæŽåæ§ïŒ
-
docker-compose.prod.ymlã«mem_limit/cpusã§ãªãœãŒã¹å¶éã远å ãã
| ç« | æ¬ãã€ã©ãŒãã¬ãŒããžã®åæ ç®æ |
|---|---|
| Chapter 2: Tooling | Next.js å èµãã«ãã·ã¹ãã æ¡çšïŒVite/Webpack ãåå¥ç®¡çããªãïŒ |
| Chapter 3: Architecture | C4ã¢ãã«ã«åºã¥ãã nginx / Frontend / Backend / DB / Auth ã®åé¢ |
| Chapter 4: Networking | HTTP ã¡ãœããã»ã¹ããŒã¿ã¹ã³ãŒãïŒitems.pyïŒãWebSocketïŒws.pyïŒãSSEïŒsse.pyïŒãã»ãã¥ãªãã£ããããŒïŒnginx.confïŒãfetchWithRetryïŒfetch.tsïŒãnginx ã«ãã immutable ãã£ãã·ã¥ |
| Chapter 5: Rendering | Server Component ã§ã®ãµãŒããŒãµã€ãèªèšŒãã§ãã¯ïŒdashboard/*/page.tsxïŒ |
| Chapter 7: Robust CSS | CSS ã¬ã€ã€ãŒã»CSS 倿°ã«ããããŒã¹ã¹ã¿ã€ã«ïŒglobals.cssïŒãã¬ã€ã¢ãŠããšã³ã³ããŒãã³ãã®åé¢ |