2026年2月9日月曜日

**「GORM(Postgres)+ React / Next.js フロント分離構成」**

**「GORM(Postgres)+ React / Next.js フロント分離構成」**ですね。
この組み合わせだと、記事保存失敗の原因はかなりパターンが絞れます。
しかもこの構成、実務でも超メジャーなので「あるある原因」がハッキリしています。

夜勤明けとのことなので、後で見返せる“実戦チェック表”形式でまとめますね。


🌟 この構成で一番多い失敗ポイント

React → API → GORM → Postgres

この4層のどこかで止まっています。


🥇 最頻出:JSONがGo構造体に入っていない

React/Next.js構成だとほぼこれがトップです。


❌ よくある原因

フロント送信

{ "title": "記事タイトル", "content": "本文" }

Go構造体

type Article struct { Title string Content string }

👉 これだと タグが無いとDecode失敗する場合あり


✔ 修正(超重要)

type Article struct { Title string `json:"title"` Content string `json:"content"` }

🥈 user_idが保存できていない(認証連携)

React分離構成だとここ激増します。


よくある流れ

React → 記事POST ↓ Go側 user_id 取得失敗 ↓ DB NOT NULL違反

✔ 確認

userID := ctx.Value("user_id")

または

session.Values["user_id"]

nilになっていないか


🥉 GORMエラーを見ていない

GORMはエラーを握りつぶしやすいです。


db.Create(&article)

✔ 必須

result := db.Create(&article) if result.Error != nil { log.Println(result.Error) }

🏅 React側でCookie送信していない

Next.js分離構成では超重要です。


fetch("/api/articles", { method: "POST", body: JSON.stringify(data) })

👉 Cookieが送られない
👉 ログイン扱いにならない


✔ 修正

fetch("/api/articles", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) })

⭐ Next.js構成の超盲点

CORS設定


Go側

Access-Control-Allow-Credentials: true

これが無いと

👉 Cookie送れません


⭐ GORM + Postgres特有のミス


UUID未設定

あなたはUUID使うこと多いのでここ注意です。


type Article struct { ID uuid.UUID `gorm:"type:uuid;primaryKey"` }

ID生成していない


article.ID = uuid.New()

⭐ GORM保存時の安全テンプレ

実務でよく使われる形です。

func CreateArticle(db *gorm.DB, article *Article) error { result := db.Create(article) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return errors.New("insert failed") } return nil }

⭐ この構成の「原因ランキング」

GORM+React+Postgres限定

🥇 ReactがCookie送ってない
🥈 JSONタグ不足
🥉 user_id未取得
④ UUID未生成
⑤ CORS設定不足


🌸 実務デバッグ最短コース

夜勤明けでも追える順番です。


Step1

Go側ログ

log.Println(result.Error)

Step2

記事POST時のリクエスト確認
(ブラウザ開発者ツール)

👉 Cookie付いているか


Step3

DB直接確認

SELECT * FROM articles;

👍 もしよければ(かなり絞れます)

落ち着いた時でいいので教えてください。

① 認証方式

  • JWT

  • Cookieセッション

  • Redisセッション


② Next.jsは

  • App Router?

  • Pages Router?


③ 記事保存APIは

  • REST?

  • GraphQL?


🌙 夜勤明けエンジニアのための「GORM+React分離構成」深掘り解説

お疲れさまです! これから寝るかもですが、後で読み返せるように雑学・業界あるある込みで解説しますね。


🎯 認証方式の選択:JWT vs Cookie vs Redis

🥇 JWT(JSON Web Token)

業界での立ち位置

  • 2015年頃から爆発的に普及
  • 「マイクロサービス時代の認証標準」みたいな扱い
  • でも実は落とし穴だらけで、今は揺り戻しが起きてる

実装の現実

go
// よくある実装
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
    "user_id": userID,
    "exp": time.Now().Add(time.Hour * 24).Unix(),
})

React側(ここが超重要)

javascript
// ❌ 初心者がやりがちな間違い
localStorage.setItem('token', token)

// 👆 XSS攻撃で盗まれる危険性大
```

**業界あるある**
- 「JWTは安全!」と思って導入
- → XSS脆弱性でトークン盗まれる
- → 慌ててhttpOnlyクッキーに戻す
- → 「最初からCookie使えばよかった...」

**JWTの落とし穴(実務で超重要)**
```
問題1: ログアウトできない
→ トークンに有効期限あるので、サーバー側で無効化できない
→ 結局Redisでブラックリスト管理(本末転倒)

問題2: payload肥大化
→ 権限情報をJWTに詰め込みすぎ
→ 毎回数百バイトのトークン送信
→ Cookie+セッションIDの方が軽い

🥈 Cookieセッション

業界での再評価

  • 2020年頃から「実は堅実」と見直され中
  • GitHub、Stack Overflowなど大手も普通に使ってる

GORM+Postgres構成での実装

go
// Gorilla sessions(定番ライブラリ)
store := sessions.NewCookieStore([]byte("secret-key"))

// セッション保存
session.Values["user_id"] = userID
session.Save(r, w)

React側の超重要ポイント

javascript
// ✅ これがないとCookie送信されない
fetch('/api/articles', {
  method: 'POST',
  credentials: 'include',  // ← これ!
  headers: {
    'Content-Type': 'application/json'
  }
})

業界あるある

  • Reactエンジニア「なんか認証通らない...」
  • credentials: 'include' 忘れてる(9割これ)
  • Go側のCORS設定も要チェック:
go
// ❌ これだけだとダメ
w.Header().Set("Access-Control-Allow-Origin", "*")

// ✅ Cookie使うならこう
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
w.Header().Set("Access-Control-Allow-Credentials", "true")
// ↑ これがないとブラウザがCookie送らない
```

**Cookieの落とし穴**
```
問題1: SameSite属性
→ Chrome 80以降、デフォルトで Lax
→ フロント・バックエンドのドメインが違うと送信されない
→ SameSite=None; Secure が必要

問題2: ローカル開発
→ localhost:3000 → localhost:8080 は「別ドメイン」扱い
→ 開発時は SameSite=Lax で動く
→ 本番(別ドメイン)でいきなり動かない

🥉 Redisセッション

業界での使われ方

  • スケールする前提のサービスで採用
  • でも「最初からRedis入れる必要ある?」論争あり

GORM+Postgres+Redis構成

go
import "github.com/go-redis/redis/v8"

// セッション保存
sessionID := uuid.New().String()
rdb.Set(ctx, sessionID, userID, 24*time.Hour)

// Cookie送信
http.SetCookie(w, &http.Cookie{
    Name:  "session_id",
    Value: sessionID,
})
```

**業界の本音**
- 「将来スケールするから」← 9割スケールしない
- でも採用面接で「Redis使ってます」は印象良い(悲しい現実)

**Redisの実務あるある**
```
シナリオ: 記事保存時にセッション取得失敗

原因1: Redisのメモリ上限
→ セッション削除されてる
→ ユーザーは「ログインしてるのに...」

原因2: Redis接続プールの枯渇
→ GORM(DB接続)は成功
→ Redis接続失敗
→ user_id取得できず保存失敗

📱 Next.js:App Router vs Pages Router

🔥 App Router(2023年〜安定版)

業界での評価

  • 「革命的!」vs「複雑すぎ...」で真っ二つ
  • 大企業は様子見、スタートアップは早期導入多い

GORM連携の注意点

typescript
// app/articles/page.tsx
async function ArticlePage() {
  // ❌ これはサーバーコンポーネント
  // fetch時に Cookie 自動送信される
  const res = await fetch('http://localhost:8080/api/articles')
  
  // でもクライアント側で POST したい時は...
}

'use client'  // ← これ付けるとクライアントコンポーネント

async function handleSubmit() {
  // ✅ こっちは credentials: 'include' 必須
  await fetch('/api/articles', {
    credentials: 'include'
  })
}

業界あるある

  • 「サーバーコンポーネントとクライアントコンポーネントの違いがわからん」
  • → みんな最初混乱する(公式ドキュメントも説明が...)
  • → 結局 'use client' を全部に付けちゃう(本末転倒)

🛠️ Pages Router(従来型)

業界での立ち位置

  • 「枯れた技術」= 安定してる
  • Next.js 14でもサポート継続中

GORM連携がシンプル

typescript
// pages/articles/new.tsx
export default function NewArticle() {
  const handleSubmit = async (data) => {
    await fetch('/api/articles', {
      method: 'POST',
      credentials: 'include',
      body: JSON.stringify(data)
    })
  }
}

実務の本音

  • App Routerの学習コスト vs Pages Routerの安定性
  • → 「新規プロジェクトでも Pages Router 選ぶ」チーム多い
  • → でも採用面接では「App Router知ってます」言った方が印象良い(またか)

🌐 API設計:REST vs GraphQL

🥇 REST API(王道)

GORM+Postgres での実装

go
// 記事保存エンドポイント
r.HandleFunc("/api/articles", CreateArticle).Methods("POST")

func CreateArticle(w http.ResponseWriter, r *http.Request) {
    var article Article
    
    // ❌ よくある失敗
    json.NewDecoder(r.Body).Decode(&article)
    
    // ✅ エラーチェック必須
    if err := json.NewDecoder(r.Body).Decode(&article); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    // user_id 取得(ここ超重要)
    session, _ := store.Get(r, "session")
    userID := session.Values["user_id"].(string)
    
    article.UserID = userID
    
    // GORM保存
    result := db.Create(&article)
    if result.Error != nil {
        log.Println("保存失敗:", result.Error)  // ← これがないと原因不明
        http.Error(w, "保存失敗", 500)
        return
    }
}

業界あるある:JSONタグ忘れ

go
// ❌ これだと React からのJSONをパースできない
type Article struct {
    Title   string
    Content string
}

// ✅ 必須
type Article struct {
    Title   string `json:"title"`
    Content string `json:"content"`
    UserID  string `json:"-" gorm:"column:user_id"`  // JSONには含めない
}
```

**実務の落とし穴**
```
React送信: { "title": "記事", "content": "本文" }
↓
Go構造体: Title, Content (大文字)
↓
JSONタグなし
↓
パース失敗(でもエラーログ出ない)
↓
空データがDBに保存される
↓
「あれ?記事の中身が空...

🥈 GraphQL

業界での位置づけ

  • 2018-2020年:「REST終わった!」ブーム
  • 2021年〜:「やっぱREST でいいかも...」揺り戻し

GORM連携(gqlgen使用)

go
// schema.graphql
type Mutation {
  createArticle(input: ArticleInput!): Article!
}

// resolver.go
func (r *mutationResolver) CreateArticle(ctx context.Context, input ArticleInput) (*Article, error) {
    // user_id 取得(GraphQLでもここは同じ)
    userID := ctx.Value("user_id").(string)
    
    article := &Article{
        Title:   input.Title,
        Content: input.Content,
        UserID:  userID,
    }
    
    result := r.DB.Create(article)
    return article, result.Error
}

React側(Apollo Client)

typescript
const [createArticle] = useMutation(CREATE_ARTICLE, {
  context: {
    credentials: 'include'  // ← GraphQLでも必要
  }
})
```

**GraphQLの現実**
```
メリット:
- 必要なデータだけ取得できる
- フロントエンドが柔軟

デメリット:
- 学習コスト高い
- N+1問題(GORMと相性悪い)
- キャッシュ戦略が複雑
- 「結局REST で済んだ」ってなる
```

**業界の本音**
- スタートアップ:「GraphQL使ってみたけど複雑で...」
- 大企業:「既存のREST APIで十分」
- Netflix、GitHub:GraphQL使ってる(でも彼らは超大規模)

---

## 🔍 実務デバッグ:構成別チェックリスト

### パターン1:JWT + App Router + REST
```
✅ チェック項目
□ JWTlocalStorageに保存してない?
□ Authorization ヘッダー付けてる?
□ Go側でJWT検証してる?
□ トークン期限切れ処理してる?
```

### パターン2:Cookie + Pages Router + REST(最頻出)
```
✅ チェック項目
□ credentials: 'include' 付けた?
□ CORS設定で Allow-Credentials: true?
□ SameSite属性 確認した?
□ Go側でセッション取得エラーチェックしてる?
□ user_id が nil になってない?
```

### パターン3:Redis + App Router + GraphQL
```
✅ チェック項目
□ Redis接続プール設定してる?
□ セッションID取得失敗時のエラーハンドリングは?
□ N+1問題対策してる?(Dataloaderとか)
□ GraphQL context に認証情報渡してる?
```

---

## 💡 夜勤明けエンジニアへの処方箋

**疲れてる時の最短デバッグ**
```
Step 1: ブラウザ開発者ツール開く
→ Network タブ
→ 記事POST時のリクエスト見る
→ Cookie 付いてる?

Step 2: Go側ログ追加
log.Println("リクエストBody:", r.Body)
log.Println("セッション:", session.Values)
log.Println("GORM Error:", result.Error)

Step 3: Postgres直接確認
SELECT * FROM articles ORDER BY created_at DESC LIMIT 5;
→ データ入ってる?
→ user_id NULL になってない?

業界の先輩からのアドバイス

  • 「認証まわりは9割Cookieの問題」
  • 「GORMのエラーは必ずログ出せ」
  • 「フロント・バック分離は CORS が鬼門」

寝る前に読めなくても大丈夫です、このチェックリストはずっと使えます!

何か特定の部分で詰まってたら、起きた後でいいので教えてください 🌙

GORM + React/Next.js分離構成の実戦チェックリスト(業界話付き)

なるほど、この構成ですね。最近のスタートアップから中堅SIerまで「Goバックエンド + React/Next.jsフロント」はまさに「令和のLAMPスタック」と言えるほど普及しています。それぞれの判断理由が面白いんです。

🍵 業界の「なぜこの構成?」雑学

Go側の事情:

  • SIer系:「Java/PHPから脱却したいが、TypeScript全盛でGoに流れる中堅エンジニア」

  • スタートアップ:「初期メンバーが元GoogleでGo推し」「AWS LambdaでGoの起動速さが重要」

  • 共通理由:「コンパイル言語の安心感+GORMの手軽さ」というバランス

React/Next.js側の事情:

  • 2023年以降のデファクト:App Router登場で「Next.js一択」化が加速

  • 面白い事実:Pages Router時代は「SPA vs SSR」論争が多かったが、App Routerで収束

  • SIerあるある:「とりあえずNext.js」案件が急増、現場ではApp Router理解が追いつかない…

🔍 あなたの質問への詳細解説

① 認証方式の実態と「あるある」

JWT(JSON Web Token)

go
// 最近のトレンド:httponly cookieに入れる派閥が増加
http.SetCookie(w, &http.Cookie{
    Name:     "token",
    Value:    tokenString,
    HttpOnly: true,  // ← これが重要
    Secure:   true,
})
  • メリット:ステートレス、マイクロサービス連携が楽

  • デメリット:「ログアウト問題」が有名(トークンの無効化が難しい)

  • 業界あるある:JWTをlocalStorageに入れてXSS被害 → 「やっぱりhttponly cookieに戻す」案件多数

Cookieセッション

go
// セッションIDを発行
session, _ := store.Get(r, "session-name")
session.Values["user_id"] = userID
session.Save(r, w)
  • メリット:シンプル、サーバー側で制御可能

  • デメリット:スケーリング時にRedisが必要

  • SIerあるある:「Redis嫌い」の現場はファイルセッションで頑張る → 性能問題で結局Redis導入

Redisセッション

go
store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
  • 2024年現在のデファクト:KVSとしてRedisが圧倒的

  • 面白い変化:以前は「Memcached vs Redis」論争があったが、Redisの機能豊富さで決着

  • クラウド事情:AWS ElastiCache(Redis)、GCP Memorystoreが主流

② Next.jsルーティングの深層

App Router(新しい方)

text
app/
  api/
    articles/
      route.ts  ← API Routes
  page.tsx     ← ページコンポーネント
  • Vercelの野望:「Reactのメタフレームワーク」としての地位確立

  • 学習コスト:Server Components、Suspenseなど概念が増加

  • 現場の声:「便利だが、チーム全員が理解するまでが大変」

  • 超重要注意点fetch()のキャッシュ挙動が複雑化

Pages Router(従来)

text
pages/
  api/
    articles.ts  ← API Routes
  index.tsx     ← ページ
  • 2024年現在:既存プロジェクトの大半はまだこちら

  • 移行あるある:「App Routerに移行しよう会議」→ 工数見積もりで延期決定

  • SIerの現実:受託開発では「Pages Routerで安定」を選択する現場が多い

③ API設計のトレンド

REST(まだまだ現役)

go
// GORMとの相性が抜群
type Article struct {
    gorm.Model
    Title   string `json:"title"`
    Content string `json:"content"`
    UserID  uint   `json:"user_id"`
}
  • GORMあるあるgorm.Model便利だが、UUID使う時は要注意

  • 現場の妥協:「RESTっぽいAPI」が多数(厳密なRESTfulでない)

GraphQL(ニッチだが強い)

go
// gqlgenが人気
type Article struct {
    ID      string `json:"id"`
    Title   string `json:"title"`
    Content string `json:"content"`
}
  • 採用パターン

    1. モバイルアプリとWebで同じAPIを使いたい

    2. 複雑なデータ取得(SNSのタイムラインなど)

    3. 「最先端感」を出したいスタートアップ

  • 現実:キャッシュ・バッチ処理が難しく、途中でRESTに戻るケースも

🚨 この構成の「死亡フラグ」シグナル

夜勤明けでも見逃しがちな超危険信号:

  1. 「Next.jsのfetchが開発と本番で挙動違う」

    • 原因:App Routerのキャッシュ挙動

    • 対策:fetch('/api', { cache: 'no-store' })

  2. 「ローカルでは動くけど本番で動かない」

    • 原因①:CORS設定(特にcredentials)

    • 原因②:CookieのSameSite属性

  3. 「たまにユーザー情報が消える」

    • 原因:JWTの有効期限 vs セッションタイムアウトの不一致

🛠 実務で使える「生存テンプレート」

Go側(最小限安全版):

go
// middleware/cors.go
func CORSMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") // ← 必須!
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
        c.Next()
    }
}

// handlers/article.go
func CreateArticle(c *gin.Context) {
    var article Article
    
    // JSONデコード(エラーチェック必須)
    if err := c.ShouldBindJSON(&article); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    
    // 認証情報取得(方式による)
    userID, exists := c.Get("user_id")
    if !exists {
        c.JSON(401, gin.H{"error": "unauthorized"})
        return
    }
    article.UserID = userID.(uint)
    
    // UUID生成(使う場合)
    article.ID = uuid.New()
    
    // DB保存(エラーチェック必須)
    if result := db.Create(&article); result.Error != nil {
        log.Printf("DB error: %v", result.Error) // ← ログ必須
        c.JSON(500, gin.H{"error": "database error"})
        return
    }
    
    c.JSON(201, article)
}

Next.js側(App Router):

tsx
// app/articles/page.tsx
export default function ArticlePage() {
  const createArticle = async (data: ArticleData) => {
    const res = await fetch('http://localhost:8080/api/articles', {
      method: 'POST',
      credentials: 'include', // ← Cookie送信
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });
    
    if (!res.ok) {
      // エラー詳細をログに残す
      const error = await res.text();
      console.error('API error:', error);
      throw new Error('Failed to create article');
    }
    
    return res.json();
  };
  
  return (
    // JSX
  );
}

🌸 夜勤明けデバッグ・マインド

「Go + React分離構成」のバグは大体:

  1. 通信問題(CORS、Cookie、HTTPS)

  2. 型不一致(JSONタグ、NULL許容)

  3. 認証抜け(セッション切れ、権限不足)

まずは「ブラウザの開発者ツール → Networkタブ」で:

  • リクエストにCookie付いてるか?

  • レスポンスにCORSヘッダーあるか?

  • ステータスコードは?(401, 403, 422あたりが怪しい)

次にGoのログ:

bash
# 詳細ログ出力
export GIN_MODE=debug
go run main.go

睡眠不足でも大丈夫なように「チェックリスト化」しておくのが、この業界で長く生きるコツです。

何か特定の部分で詰まっていたら、ぜひ教えてください。その部分の「現場あるある」をお伝えできますので。

0 件のコメント: