Udemy講座のサンプルをGo言語で書き直してみた

2025/01/27に公開

はじめに

昨年、Dockerの勉強をするために、「Linux とネットワークの基礎から学ぶ Docker 入門」を受けたところ、Webアプリの基礎的なところが、かなり勉強になりました。この課程ではバックエンドをRubyのフレームワークであるSinatraで作成しました。
https://www.udemy.com/course/docker-from-linux-and-networking/

現在Go言語を勉強するため、Ruby SinatraのプログラムをGo言語に書き直してみました。Go言語はシンプルさと効率性が魅力的であり、特にバックエンド開発において新しい視点を得られると感じました。

書き直してみて

基本的なことが多いので、ソースの細かい説明は控えますが、書き直してみて思ったことを書きます。以下は特に印象に残った点です。

  • 標準の機能だけでHTTP通信を書くことができる
    他の言語では、追加のライブラリを入れる必要があることが多いですが、Go言語では標準の機能だけで実装できる点が非常に良いです。これにより、初学者でも環境構築や依存関係の管理に悩まされずに取り組むことができます。

  • 非常に少ないコードで簡潔に書くことができる
    元々C#やC++を使うことが多かったので、それと比べて簡潔に書くことができる気がしました。例えば、簡単なWebサーバを立ち上げる際のコード量は驚くほど少なく、コードの可読性も高いと感じました。

  • 型が決まっていることの安心感
    これもC#やC++を使うことが多かったせいもありますが、型がきっちりしている方が安心感できます。Typescriptを勉強しているのも同じ理由です。静的型付け言語であるGo言語では、エラーを事前に検出しやすく、特に大規模なプロジェクトでの開発効率が上がると感じました。

  • ドキュメントが充実しており学習しやすい
    Go言語の公式ドキュメントやチュートリアルが非常にわかりやすく整理されており、新しい機能を学ぶ際に大きな助けとなりました。また、オンラインコミュニティも活発で、多くのリソースが共有されているのも魅力的です。

コード

main.go

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

// メッセージ構造体
type ResMessage struct {
	Message string `json:"message"`
}

// TaskItem構造体
type TaskItem struct {
	Title     string `json:"title"`
	CreatedAt string `json:"createdAt"`
}

// TaskList構造体
type TaskList struct {
	Tasks []TaskItem `json:"tasks"`
}

// 現在時刻から作成日文字列を取得
func getCreatedAt() string {
	return time.Now().Format("2006-01-02 15:04:05")
}

// Title文字列からTaskItemを作成
func NewTaskItem(task string) *TaskItem {
	return &TaskItem{
		Title:     task,
		CreatedAt: getCreatedAt(),
	}
}

// JSON文字列データからTaskItemを作成
func NewTaskItemFromJson(taskJson string) *TaskItem {
	var item TaskItem
	json.Unmarshal([]byte(taskJson), &item)
	item.CreatedAt = getCreatedAt()
	return &item
}

// クライアントから/を受信したときの処理
func handleGreet(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World! %s", time.Now())
}

// クライアントから/helloを受信したときの処理
func handleHello(w http.ResponseWriter, r *http.Request) {
	res1A := &ResMessage{
		Message: "Hello World",
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(res1A)
}

// クライアントから/tasksを受信したときの処理
func handleTasks(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		getTaskList(w, r)
	case http.MethodPost:
		addTaskList(w, r)
	}
}

// タスクリストから取得
func getTaskList(w http.ResponseWriter, _ *http.Request) {
	taskList := fetchTasks()
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(taskList)
}

// タスクリストに追加
func addTaskList(_ http.ResponseWriter, r *http.Request) {
	body, _ := io.ReadAll(r.Body)
	taskItem := NewTaskItemFromJson(string(body))
	insertTask(*taskItem)
}

// メイン
func main() {
	//データベース接続
	initDb()
	defer closeDB()
	//handle登録
	http.HandleFunc("/", handleGreet)
	http.HandleFunc("/api/hello", handleHello)
	http.HandleFunc("/api/tasks", handleTasks)
	http.ListenAndServe(":4567", nil)
}

mysql.go

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

// データベース起動
func initDb() {
	// MySQLに接続
	dsn := "myuser:password@tcp(127.0.0.1:3306)/mydb"
	var err error
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		log.Fatal(err)
	}

	// 接続を確認
	err = db.Ping()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Successfully connected to MySQL!")
}

// データベース終了
func closeDB() {
	if db != nil {
		db.Close()
	}
}

// タスクリスト取得
func fetchTasks() TaskList {
	rows, err := db.Query("SELECT title, created_at FROM tasks")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	var taskList TaskList
	for rows.Next() {
		var task TaskItem
		if err := rows.Scan(&task.Title, &task.CreatedAt); err != nil {
			log.Fatal(err)
		}
		taskList.Tasks = append(taskList.Tasks, task)
	}

	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}

	return taskList
}

// タスク挿入
func insertTask(task TaskItem) {
	title := task.Title
	// query := fmt.Sprintf("INSERT INTO tasks (title) VALUES ('%s')", title)
	// _, err := db.Query(query)
	query := "INSERT INTO tasks (title) VALUES (?)"
	_, err := db.Exec(query, title)

	if err != nil {
		log.Fatal(err)
	}
}

Discussion