◉GEMINI LABEN
●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/API / SDK
◈ API / SDK/2026-04-29上級

NestJS で Gemini API バックエンドを設計する — DI・例外フィルタ・ガードを活かす実装パターン

NestJS の DI・例外フィルタ・ガードで保守しやすい Gemini API バックエンドを構築する実装パターンを解説します。SSE ストリーミング、Flash→Pro フォールバック、指数バックオフ・リトライ、レイテンシとトークンの実測まで実コードでまとめます。

gemini-api287nestjstypescript16backendsse

✦ プレミアム記事

Gemini API を Express や薄い Hono ベースで素早く立ち上げると、最初の数週間は快適です。しかし機能が増え、認可・レート制御・複数モデルの切り替え・例外メッセージのフォーマットといった「外側」が膨らんできた頃に、コードがコントローラに溜まり始めます。私自身、個人プロダクトでも企業案件でも、ここで一度 NestJS に作り直す判断をすることが多くなりました。NestJS の DI(依存性注入)、デコレータ、例外フィルタ、ガードといった仕組みは、AI バックエンド特有の「複雑な外側」をきれいに分離するのに非常に向いているからです。

ここではGemini API を NestJS で扱うときに私が落ち着いた「現場で消耗しにくい」実装パターンを、なるべく短いコードで共有します。Express で書いた場合と何が違うのか、そしてなぜそうするのかまで踏み込んで解説します。

なぜ Gemini API には NestJS が向いているのか

NestJS はもともと Angular に近い設計思想を持つ TypeScript 向けフレームワークで、DI と「Module / Controller / Provider」の3層構造を強制してきます。これが Gemini API のような「外部依存が多いサービス」を扱うときに効いてきます。

私が NestJS を選ぶ実利的な理由は3つあります。第一に、GeminiService を Provider として注入することで、コントローラ側のテストをモックに差し替えやすくなる点です。第二に、HttpException のサブクラスを定義すれば Gemini 特有の 429・503・コンテンツ違反エラーを @Catch() で一括ハンドリングできる点。第三に、Guard を組み合わせれば API キー認証や 1 ユーザーあたりのレートリミットを Controller 側のコードを汚さずに実現できる点です。Express でこれを真面目にやると、ミドルウェアの順序やエラー伝播でハマりがちでした。

プロジェクト構成と GeminiModule の作り方

最小構成では、GeminiModule を1つ作り、その中に GeminiService(GoogleGenerativeAI クライアントのラッパー)を Provider として登録するのが基本形です。

// src/gemini/gemini.module.ts
import { Module } from '@nestjs/common';
import { GoogleGenerativeAI } from '@google/generative-ai';
import { GeminiService } from './gemini.service';
import { GeminiController } from './gemini.controller';
 
@Module({
  controllers: [GeminiController],
  providers: [
    {
      provide: 'GENAI_CLIENT',
      useFactory: () => new GoogleGenerativeAI(process.env.GEMINI_API_KEY!),
    },
    GeminiService,
  ],
  exports: [GeminiService],
})
export class GeminiModule {}

GoogleGenerativeAI 自体を Provider として切り出しているのがポイントです。テスト時には useFactory を差し替えるだけで完全なモッククライアントを注入でき、ネットワークを叩かずユニットテストが書けます。@google/generative-ai を直接 import して new するコードがコントローラに散らばると、これができなくなります。

GeminiService は単純に「モデル選択・プロンプト整形・レスポンス整形」の責務だけ持たせます。ストリーミングと一括取得をメソッド分割しておくと、後述の SSE 対応がきれいに書けます。

// src/gemini/gemini.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { GoogleGenerativeAI } from '@google/generative-ai';
 
@Injectable()
export class GeminiService {
  constructor(
    @Inject('GENAI_CLIENT') private readonly client: GoogleGenerativeAI,
  ) {}
 
  async generate(prompt: string, model = 'gemini-2.5-flash') {
    const m = this.client.getGenerativeModel({ model });
    const res = await m.generateContent(prompt);
    return res.response.text();
  }
 
  async *stream(prompt: string, model = 'gemini-2.5-flash') {
    const m = this.client.getGenerativeModel({ model });
    const res = await m.generateContentStream(prompt);
    for await (const chunk of res.stream) {
      yield chunk.text();
    }
  }
}

非同期ジェネレータを返す stream メソッドを用意しておくと、後で SSE でも WebSocket でも好きな伝送方式に流し込めます。フレームワークに依存しない形で逃げ場を作っておくのは、大事な現場のコツです。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦Flash → Pro のフォールバックを Service に閉じ込め、過負荷時だけ自動で切り替える実装
✦指数バックオフ + ジッタ付きリトライを Interceptor に集約し、Controller と Service を汚さない設計
✦1.2 万リクエストの実測: p50/p95 レイテンシとトークン消費、Pro へのフォールバック発火率の生データ
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

◈ API / SDK2026-05-09
Gemini Live API で画面共有しながら音声で対話する『AI ペアプログラマー』を TypeScript で構築する
Gemini Live API と getDisplayMedia で『画面を見せながら音声で相談できる AI ペア』を TypeScript で構築する手順を、コスト最適化と落とし穴まで含めて実装プレイブックにまとめました。
◈ API / SDK2026-05-03
Gemini API × Drizzle ORM で型安全な AI チャットストアを構築する — マルチターン保存・スレッド分岐・ベクトル検索の本番設計
Gemini API のマルチターン会話を Drizzle ORM で型安全に保存・検索する本番設計を解説します。ストリーミング保存、スレッド分岐、Function Calling 履歴、pgvector 連携、マイグレーション戦略まで、実装で詰まりやすい論点を実コード付きでまとめました。
◈ API / SDK2026-10-16
models は 200 なのに generateContent だけ 403 になる日 — 切り分けの順番を先に決めておく
モデル一覧の取得は 200 で通るのに、生成リクエストだけが 403 permission_denied で止まるときの切り分け手順です。キー・プロジェクト・請求の順に確かめる小さな診断スクリプトと、リトライしてはいけない理由、サポートへ渡す材料の揃え方をまとめました。