Firebaseプロジェクトの設定
Firebaseプロジェクトを作成する
Firebase 構築 > Authenticationを開く

プロバイダから「匿名」を選択し、有効にする

次に、プロバイダの追加で、「Google」を選択し、有効にする

GCPサービスアカウントの所得
- Firebaseプロジェクトと同名で、GCPプロジェクトが作成されている
- IAMには、Firebase管理用のサービスアカウントが作成されている
- private keyを作成する、これは、後述のfirebase-adminの初期化に使用する
Next.jsアプリの構築
Next.jsアプリを生成する
% npx create-next-app@latest apps/web
✔ Would you like to use TypeScript? … No / Yes
✔ Which linter would you like to use? › ESLint
✔ 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? (recommended) … No / Yes
✔ Would you like to customize the import alias (`@/*` by default)? … No / Yes
Creating a new Next.js app in xxx
Using npm.
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
added 335 packages, and audited 336 packages in 17s
137 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Success! Created web at xxx
apps/webフォルダ以下のファイル構成
apps/web
apps/web/app
apps/web/app/favicon.ico
apps/web/app/layout.tsx
apps/web/app/page.tsx
apps/web/app/globals.css
apps/web/postcss.config.mjs
apps/web/node_modules
apps/web/next-env.d.ts
apps/web/README.md
apps/web/public
apps/web/public/file.svg
apps/web/public/vercel.svg
apps/web/public/next.svg
apps/web/public/globe.svg
apps/web/public/window.svg
apps/web/.gitignore
apps/web/package-lock.json
apps/web/package.json
apps/web/tsconfig.json
apps/web/eslint.config.mjs
apps/web/next.config.ts
NestJS作成ブログと合わせて、以下の構成となった
package.jsonを持つアプリとしては以下の3つ
- api
- web
- @app/adapters
root/
├─ apps/
│ ├─ api/ ← NestJS バックエンド(本物の API サーバー)
│ │ ├─ src/chat/ ← /chat エンドポイント関連
│ │ ├─ src/auth/ ← Firebase Guard, 認証まわり
│ │
│ └─ web/ ← Next.js フロントエンド
│ ├─ app/ ← Next.js App Router のページ
│ │ └─ api/ ← プロキシ用 API Routes(CORS回避が必要なら)
│ ├─ components/ ← Reactコンポーネント(ChatBox, Avatarなど)
│ ├─ lib/ ← フロント専用のユーティリティ(API呼び出し, firebase.ts)
│ └─ public/ ← 画像・静的ファイル
│
├─ libs/ ← 共通ライブラリ群(apps/api と apps/web 両方で使う)
│ └─ adapters/ ← LLM 抽象化 (LlmAdapter, OpenAiLlmAdapter, GeminiLlmAdapter)
│
└─ package.json ← ルートの依存管理 (pnpm workspace)
Firebaseによる認証の実装
firebaseパッケージのインストール
% pnpm add firebase --filter web
% pnpm add firebase-admin --filter api
% pnpm add firebase-admin --filter @app/adapters
web: フロントエンド用のFirebase認証機能関数
import { initializeApp } from 'firebase/app';
import {
getAuth,
signInAnonymously,
GoogleAuthProvider,
signInWithPopup
} from 'firebase/auth';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY!,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN!,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID!,
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
// 匿名ログイン
export async function loginAnonymously() {
const result = await signInAnonymously(auth);
return result.user;
}
// Google ログイン
export async function loginWithGoogle() {
const provider = new GoogleAuthProvider();
const result = await signInWithPopup(auth, provider);
return result.user;
}
// IDトークン取得(APIコール時に使用)
export async function getIdToken() {
if (!auth.currentUser) return null;
return await auth.currentUser.getIdToken();
}
FirebaseでWebアプリの作成
- Firebase Console にログイン
作成したプロジェクトを開く - 左メニュー → ⚙️ [プロジェクトの設定] をクリック
- 「自分のアプリ」セクションで Web アプリ(</> マーク) を選択
- もしまだ Web アプリを追加していないなら「アプリを追加」で Web を選ぶ
- 登録が終わると、Firebase SDK の設定スニペットが出ます:
const firebaseConfig = {
apiKey: "AIza...xxxx",
authDomain: "your-project-id.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project-id.appspot.com",
messagingSenderId: "1234567890",
appId: "1:1234567890:web:abcdef123456",
};
apps/web/.env.localにセットする
NEXT_PUBLIC_FIREBASE_API_KEY={apiKey}
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN={authDomain}
NEXT_PUBLIC_FIREBASE_PROJECT_ID={projectId}
@app/adapters: バックエンド用Firebase認証検証関数
- verifyFirebaseTokenを実装
import * as admin from 'firebase-admin';
const projectId = process.env.FIREBASE_PROJECT_ID;
const clientEmail = process.env.FIREBASE_CLIENT_EMAIL;
const privateKey = process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n');
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert({ projectId, clientEmail, privateKey }),
});
}
export async function verifyFirebaseToken(token: string) {
return await admin.auth().verifyIdToken(token);
}
api: NestJS用のFirebaseトークン検証用ミドルウェア
- リクエストヘッダーから
Authorization
を取得し、トークンがなければ認証エラーを返します。 - トークンを verifyFirebaseToken で検証し、正しければデコードしたユーザー情報を req.user にセットします。
- トークンが無効なら認証エラーを返します。
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { verifyFirebaseToken } from '@app/adapters/firebase-auth.adapter';
@Injectable()
export class FirebaseAuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const authHeader = req.headers['authorization'];
if (!authHeader) throw new UnauthorizedException('Missing token');
const token = authHeader.split(' ')[1];
try {
const decoded = await verifyFirebaseToken(token);
req.user = decoded;
return true;
} catch (e) {
throw new UnauthorizedException('Invalid token');
}
}
}
web: コントローラーの変更
- @UseGuards(FirebaseAuthGuard)により認証
import { Controller, Post, Body, UseGuards, Req } from '@nestjs/common';
import { ChatService } from './chat.service';
import { FirebaseAuthGuard } from '../auth/firebase-auth.guard';
@Controller('chat')
export class ChatController {
constructor(private readonly chatService: ChatService) {}
@UseGuards(FirebaseAuthGuard)
@Post()
async chat(@Body('message') message: string, @Req() req: any) {
return this.chatService.getReply(message, req.user.uid);
}
}
web: フロント用API 呼び出し関数
import { getIdToken } from './firebase';
export async function sendChat(message: string) {
const token = await getIdToken();
const res = await fetch('/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ message }),
});
if (!res.ok) throw new Error('API error');
return res.json();
}
web: UI用のapi実装
- UIかのリクエストを受取、バックエンドのapiとやりとりする
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const body = await req.json();
const token = req.headers.get('authorization');
const res = await fetch(process.env.NEST_API_URL + '/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: token ?? '',
},
body: JSON.stringify(body),
});
return NextResponse.json(await res.json());
}
動作確認
NestJS, Next.jsアプリとFirebase, ChatGPTが連携することを確認しました。

注意
- webがホットリロードされるので、@app/adaptersもホットリロードされると思い込み、デバッグに時間がかかった
- monorepoで、3つのアプリのpackage.json, tsconfig.jsonの設定の方法が自由度が高すぎて困難。ビルドがうまくいったと思ったら、実行がうまくいかないとか、試行錯誤が数時間続きました。