Next.jsとGoを使ったアプリケーションの構築: 記事一覧ページの実装

## はじめに
この記事では、Next.jsとGoを使用してアプリケーションを構築する方法を紹介します。具体的には、Goをバックエンドとして使用し、Next.jsをフロントエンドとして使用して、記事一覧ページを実装します。

なお、こちらは以下の続きになります。

https://qiita.com/jijimama/items/e90bcb67b271458a08d6


## バックエンドの実装
まず、Goを使用してバックエンドを構築します。以下のコードは、記事データを取得するためのエンドポイントを提供するGoのサーバーです。

```go:backend/main.go
package main

import (
	"net/http"
	"context"
	"log"
	"os"

	"github.com/gin-gonic/gin"
	"github.com/joho/godotenv"
	"github.com/jijimama/newspaper-app/ent"
	_ "github.com/lib/pq"
)

type Article struct {
    Year      int    `json:"year"`
    Month     int    `json:"month"`
    Day       int    `json:"day"`
    Content   string `json:"content"`
	Newspaper string `json:"newspaper"`
	ColumnName string `json:"column_name"`
}

func main() {
	router := gin.Default()

	// .envファイルを読み込む
	err := godotenv.Load()
	if err != nil {
		log.Fatalf("Error loading .env file: %v", err)
	}

	// 環境変数から取得
	dbHost := os.Getenv("DB_HOST")
	dbPort := os.Getenv("DB_PORT")
	dbUser := os.Getenv("DB_USER")
	dbName := os.Getenv("DB_NAME")
	dbPassword := os.Getenv("DB_PASSWORD")

	// 接続文字列を作成
	dsn := "host=" + dbHost + " port=" + dbPort + " user=" + dbUser + " dbname=" + dbName + " password=" + dbPassword + " sslmode=disable"

    //PostgreSQLに接続
	client, err := ent.Open("postgres", dsn)
	if err != nil {
		log.Fatalf("failed opening connection to postgres: %v", err)
	}
	defer client.Close()
	// Run the auto migration tool.
	if err := client.Schema.Create(context.Background()); err != nil {
		log.Fatalf("failed creating schema resources: %v", err)
	}
	
  	router.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
      		"message": "Hello, World!",
		})
  	})

	router.GET("/articles", func(c *gin.Context) {
        articles, err := getArticles(client)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "failed querying articles"})
            return
        }
        c.JSON(http.StatusOK, articles)
    })

  	router.Run(":8080")
}

func getArticles(client *ent.Client) ([]Article, error) {
    articles, err := client.Article.Query().WithNewspaper().All(context.Background())
    if err != nil {
        return nil, err
    }

    result := make([]Article, len(articles))
    for i, a := range articles {
        result[i] = Article{
            Year:       a.Year,
            Month:      a.Month,
            Day:        a.Day,
            Content:    a.Content,
            Newspaper:  a.Edges.Newspaper.Name,
			ColumnName: a.Edges.Newspaper.ColumnName,
        }
    }

    return result, nil
}
```
### 補足説明

```go
type Article struct {
    Year      int    `json:"year"`
    Month     int    `json:"month"`
    Day       int    `json:"day"`
    Content   string `json:"content"`
	Newspaper string `json:"newspaper"`
	ColumnName string `json:"column_name"`
}
```
Article構造体は、記事の情報を表現するために使用されるデータ構造です。この構造体には、以下のフィールドが含まれています：

- Year (int): 記事が発行された年を表します。
- Month (int): 記事が発行された月を表します。
- Day (int): 記事が発行された日を表します。
- Content (string): 記事の内容を表します。
- Newspaper (string): 記事が掲載された新聞の名前を表します。
- ColumnName (string): 記事が掲載されたコラムの名前を表します。

各フィールドには、JSONタグが付けられており、これにより構造体がJSON形式に変換される際のキー名が指定されています。例えば、YearフィールドはJSON形式に変換されると"year"というキーになります。

この構造体は、データベースから取得した記事データをAPIレスポンスとして返す際に使用されます。

#### ```router.GET("/articles", func(c *gin.Context) {...})``` の解説
この部分は、Ginフレームワークを使用して定義されたHTTP GETエンドポイントです。/articlesパスに対するリクエストを処理します。

**1. エンドポイントの定義:**
```go
router.GET("/articles", func(c *gin.Context) {...})
```

これは、```/articles```パスに対するGETリクエストを処理するハンドラ関数を定義しています。

**2. 記事の取得:**

```go
articles, err := getArticles(client)
```

```getArticles```関数を呼び出して、データベースから記事のリストを取得します。```client```はデータベース接続を管理するためのクライアントです。

**3. エラーハンドリング:**
```go
if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "failed querying articles"})
    return
}
```
```getArticles```関数がエラーを返した場合、HTTPステータス500 (Internal Server Error) とエラーメッセージをJSON形式でクライアントに返します。

**4. 成功時のレスポンス:**
```go
c.JSON(http.StatusOK, articles)
```

記事のリストをJSON形式でHTTPステータス200 (OK) と共にクライアントに返します。

#### ```func getArticles(client *ent.Client) ([]Article, error)``` の解説
この関数は、データベースから記事のリストを取得し、Article構造体のスライスとして返します。

**1. 記事のクエリ:**
```go
articles, err := client.Article.Query().WithNewspaper().All(context.Background())
```

```client.Article.Query()```を使用して、記事をクエリします。```WithNewspaper()```は、記事と関連する新聞の情報も一緒に取得するためのメソッドです。```All(context.Background())```は、すべての記事を取得します。

**2. エラーハンドリング:**
```go
if err != nil {
    return nil, err
}
```

クエリが失敗した場合、エラーを返します。

**3. 結果の変換:**
```go
result := make([]Article, len(articles))
for i, a := range articles {
    result[i] = Article{
        Year:       a.Year,
        Month:      a.Month,
        Day:        a.Day,
        Content:    a.Content,
        Newspaper:  a.Edges.Newspaper.Name,
        ColumnName: a.Edges.Newspaper.ColumnName,
    }
}
```
データベースから取得した記事データを```Article```構造体のスライスに変換します。```a.Edges.Newspaper.Name```と```a.Edges.Newspaper.ColumnName```は、関連する新聞の名前とコラム名を取得します。

結果の返却:
```go
return result, nil
```

変換された記事のリストを返します。エラーがない場合、```nil```をエラーとして返します。

### 動作確認
http://localhost:8080/articles
にアクセスします。

以下のように取得できれば、OKです！

![スクリーンショット 2024-12-25 8.03.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3724481/e9c3a615-25e2-5eb1-6a54-effdd1a569ea.png)

## フロントエンドの実装
次に、Reactを使用してフロントエンドを構築します。以下のコードは、記事データを表示するためのReactコンポーネントです。

```tsx:articles/page.tsx
import React from 'react';

type Article = {
  id: number;
  year: number;
  month: number;
  day: number;
  content: string;
  newspaper: string;
  column_name: string;
};

async function fetchArticles(): Promise<Article[]> {
  const res = await fetch('http://backend:8080/articles');
  if (!res.ok) {
    throw new Error('Failed to fetch articles');
  }
  return res.json();
}

const ArticlesPage = async () => {
  const articles = await fetchArticles();

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-center text-2xl font-bold mb-4">コラム一覧</h1>
      <ul className="space-y-4">
        {articles.map((article) => (
          <li key={article.id} className="bg-gray-800 p-6 rounded-lg shadow-md">
            <h2 className="text-lg font-semibold text-gray-300 mb-2">{article.content}</h2>
            <p className="text-gray-400">{article.year}/{article.month}/{article.day} {article.newspaper} / {article.column_name}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default ArticlesPage;
```

### 補足説明
```tsx
type Article = {
  id: number;
  year: number;
  month: number;
  day: number;
  content: string;
  newspaper: string;
  column_name: string;
};
```
- Article 型は、記事のデータ構造を定義しています。
- 各フィールドは記事の属性を表し、型が指定されています。

```tsx
async function fetchArticles(): Promise<Article[]> {
  const res = await fetch('http://backend:8080/articles');
  if (!res.ok) {
    throw new Error('Failed to fetch articles');
  }
  return res.json();
}
```
- ```fetchArticles```関数は、バックエンドから記事のデータを非同期に取得します。
- ```fetch```関数を使用して、指定されたURLからデータを取得します。
- レスポンスが正常 ```(res.ok)```でない場合、エラーをスローします。
- 正常な場合、レスポンスをJSON形式に変換して返します。

```tsx
const ArticlesPage = async () => {
  const articles = await fetchArticles();

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-center text-2xl font-bold mb-4">コラム一覧</h1>
      <ul className="space-y-4">
        {articles.map((article) => (
          <li key={article.id} className="bg-gray-800 p-6 rounded-lg shadow-md">
            <h2 className="text-lg font-semibold text-gray-300 mb-2">{article.content}</h2>
            <p className="text-gray-400">{article.year}/{article.month}/{article.day} {article.newspaper} / {article.column_name}</p>
          </li>
        ))}
      </ul>
    </div>
  );
};
```
- ```ArticlesPage```は非同期関数として定義されたReactコンポーネントです。
- ```fetchArticles```関数を呼び出して記事のデータを取得します。
- 取得した記事データを使用して、記事の一覧を表示します。

### 動作確認
http://localhost:3000/articles
にアクセスします。

以下のように表示されればOKです！

![スクリーンショット 2024-12-25 8.08.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3724481/b517d89f-21be-6f58-e148-1d8acc2202cd.png)

## まとめ
この記事では、GoとNext.jsを使用してアプリケーションを構築する方法を紹介しました。バックエンドではGinフレームワークを使用してAPIを構築し、フロントエンドではReactを使用してデータを表示しました。これにより、効率的にデータを取得し、ユーザーに表示することができます。

ぜひ、この記事を参考にして、あなた自身のフルスタックアプリケーションを構築してみてください。