0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-12-26

はじめに

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

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

バックエンドの実装

まず、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
}

補足説明

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. エンドポイントの定義:

router.GET("/articles", func(c *gin.Context) {...})

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

2. 記事の取得:

articles, err := getArticles(client)

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

3. エラーハンドリング:

if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "failed querying articles"})
    return
}

getArticles関数がエラーを返した場合、HTTPステータス500 (Internal Server Error) とエラーメッセージをJSON形式でクライアントに返します。

4. 成功時のレスポンス:

c.JSON(http.StatusOK, articles)

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

func getArticles(client *ent.Client) ([]Article, error) の解説

この関数は、データベースから記事のリストを取得し、Article構造体のスライスとして返します。

1. 記事のクエリ:

articles, err := client.Article.Query().WithNewspaper().All(context.Background())

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

2. エラーハンドリング:

if err != nil {
    return nil, err
}

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

3. 結果の変換:

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.Namea.Edges.Newspaper.ColumnNameは、関連する新聞の名前とコラム名を取得します。

結果の返却:

return result, nil

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

動作確認

http://localhost:8080/articles
にアクセスします。

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

スクリーンショット 2024-12-25 8.03.21.png

フロントエンドの実装

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

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;

補足説明

type Article = {
  id: number;
  year: number;
  month: number;
  day: number;
  content: string;
  newspaper: string;
  column_name: string;
};
  • Article 型は、記事のデータ構造を定義しています。
  • 各フィールドは記事の属性を表し、型が指定されています。
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形式に変換して返します。
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

まとめ

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

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?