Go 語言系列(八):資料庫整合

這是 Go 語言從零到 Web 應用系列的第八篇。上一篇我們建立了結構清晰的專案,但資料存在記憶體中。這篇要把資料存進真正的資料庫。 為什麼選 SQLite? 對於教學和小型應用來說,SQLite 是完美的選擇: 零配置:不需要安裝資料庫伺服器 單一檔案:整個資料庫就是一個檔案 效能優秀:對於大多數應用場景都足夠快 SQL 相容:學到的 SQL 知識可以直接用在 PostgreSQL、MySQL 而且 Go 的 database/sql 是統一的資料庫介面,只要換一個 driver 就能切換到其他資料庫。 database/sql 介紹 database/sql 是 Go 標準庫提供的資料庫抽象層。它定義了統一的介面,不同的資料庫只需要提供對應的 driver。 常見的 driver: SQLite: github.com/mattn/go-sqlite3(CGO)或 modernc.org/sqlite(純 Go) PostgreSQL: github.com/lib/pq 或 github.com/jackc/pgx MySQL: github.com/go-sql-driver/mysql 我們使用純 Go 實作的 SQLite driver,不需要 CGO: go get modernc.org/sqlite 連接資料庫 import ( "database/sql" _ "modernc.org/sqlite" // 註冊 SQLite driver ) func main() { db, err := sql.Open("sqlite", "./bookapi.db") if err != nil { log.Fatal(err) } defer db.Close() // 驗證連線 if err := db.Ping(); err != nil { log.Fatal(err) } fmt.Println("Database connected!") } 注意 _ "modernc.org/sqlite" 這行——前綴的 _ 表示只引入套件的副作用(註冊 driver),不直接使用它的任何匯出名稱。 ...

2026年2月24日 · 7 min · 1315 words · Jack

Go 語言系列(七):專案結構與路由

這是 Go 語言從零到 Web 應用系列的第七篇。上一篇我們建立了第一個 API Server,但所有程式碼都在一個檔案裡。這篇要來學如何組織一個可維護的專案結構。 專案目錄結構 Go 社群雖然沒有強制的專案結構,但有一個被廣泛採用的慣例。以下是我們的 Web 應用將使用的結構: bookapi/ ├── cmd/ │ └── server/ │ └── main.go # 程式進入點 ├── internal/ │ ├── handler/ │ │ └── book.go # HTTP handler │ ├── middleware/ │ │ ├── logging.go # 日誌中間件 │ │ └── recovery.go # Panic recovery 中間件 │ ├── model/ │ │ └── book.go # 資料模型 │ └── store/ │ └── book.go # 資料存取層 ├── go.mod ├── go.sum └── .env # 環境變數(不進版控) 目錄說明 cmd/:可執行程式的進入點。每個子目錄對應一個執行檔 internal/:私有程式碼,Go 編譯器會阻止外部套件引用這個目錄下的程式碼 handler/:HTTP 請求處理器 middleware/:中間件 model/:資料模型定義 store/:資料存取邏輯 初始化專案 mkdir bookapi && cd bookapi go mod init bookapi 定義資料模型 internal/model/book.go package model import "time" type Book struct { ID int `json:"id"` Title string `json:"title"` Author string `json:"author"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type CreateBookRequest struct { Title string `json:"title"` Author string `json:"author"` } func (r CreateBookRequest) Validate() map[string]string { errors := make(map[string]string) if r.Title == "" { errors["title"] = "title is required" } if r.Author == "" { errors["author"] = "author is required" } return errors } type UpdateBookRequest struct { Title *string `json:"title"` Author *string `json:"author"` } 使用獨立的 Request 型別而非直接用 Book,可以明確區分「客戶端送來的資料」和「系統內部的資料」。UpdateBookRequest 中的指標欄位讓我們能區分「沒有提供」和「設為空字串」。 ...

2026年2月24日 · 6 min · 1238 words · Jack

Go 語言系列(六):HTTP 基礎與第一個 Web Server

這是 Go 語言從零到 Web 應用系列的第六篇,也是實戰篇的開始。從這篇開始,我們要用前五篇學到的 Go 基礎,開始建構一個真正的 Web 應用。 為什麼 Go 適合做 Web 開發? Go 的標準庫 net/http 已經是一個生產等級的 HTTP 伺服器。不像其他語言需要額外安裝框架(如 Python 的 Flask、Node.js 的 Express),Go 開箱即用就能處理 HTTP 請求。 這個標準庫的品質高到什麼程度?許多知名的 Go Web 框架(如 Gin、Echo)本質上都是在 net/http 之上加了一層便利功能。 最簡單的 Web Server package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World!") }) fmt.Println("Server starting on :8080") http.ListenAndServe(":8080", nil) } 執行後用瀏覽器打開 http://localhost:8080,你就會看到 “Hello, World!"。 就是這麼簡單——5 行核心程式碼就建好了一個 HTTP 伺服器。 理解 Handler http.Handler 介面 Go 的 HTTP 處理核心是 Handler 介面: ...

2026年2月24日 · 5 min · 952 words · Jack

Go 語言系列(五):錯誤處理與併發

這是 Go 語言從零到 Web 應用系列的第五篇,也是基礎篇的最後一篇。這篇要學的是 Go 最有特色的兩個主題:錯誤處理和併發。 錯誤處理 Go 的錯誤哲學 Go 沒有 try/catch。它的哲學是:錯誤是正常的程式流程,應該被明確處理,而不是被「拋出」和「捕捉」。 Go 使用多值回傳來傳遞錯誤: result, err := doSomething() if err != nil { // 處理錯誤 return err } // 使用 result 這個模式你在 Go 程式碼中會看到無數次。 error 介面 Go 的 error 是一個簡單的介面: type error interface { Error() string } 任何實作了 Error() string 方法的型別都是 error。 建立錯誤 import ( "errors" "fmt" ) // 方式一:errors.New err := errors.New("something went wrong") // 方式二:fmt.Errorf(可以格式化訊息) name := "config.json" err = fmt.Errorf("file not found: %s", name) 自訂 Error 型別 type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message) } func validateAge(age int) error { if age < 0 || age > 150 { return &ValidationError{ Field: "age", Message: "must be between 0 and 150", } } return nil } 錯誤包裝(Error Wrapping) Go 1.13 引入了錯誤包裝機制,讓你可以在傳遞錯誤時加入上下文: ...

2026年2月24日 · 6 min · 1181 words · Jack

Go 語言系列(四):資料結構

這是 Go 語言從零到 Web 應用系列的第四篇。上一篇學了基礎語法,這篇要來認識 Go 的核心資料結構。 Array(陣列) Array 是固定長度的同型別元素集合。 // 宣告陣列(長度是型別的一部分) var scores [5]int // [0, 0, 0, 0, 0] // 初始化 scores = [5]int{90, 85, 78, 92, 88} // 簡短宣告 names := [3]string{"Alice", "Bob", "Charlie"} // 讓編譯器計算長度 primes := [...]int{2, 3, 5, 7, 11} // 存取元素 fmt.Println(scores[0]) // 90 scores[1] = 95 // 取得長度 fmt.Println(len(scores)) // 5 Array 在 Go 中較少直接使用,因為長度固定且是值語意(傳遞時會複製整個陣列)。大多數情況我們用 Slice。 Slice(切片) Slice 是 Go 中最常用的集合型別,可以視為動態長度的陣列。 建立 Slice // 從 array 建立 arr := [5]int{1, 2, 3, 4, 5} s := arr[1:4] // [2, 3, 4](左包含,右不包含) // 直接宣告 nums := []int{10, 20, 30} // 使用 make(指定長度和容量) s1 := make([]int, 5) // 長度 5,容量 5 s2 := make([]int, 3, 10) // 長度 3,容量 10 長度 vs 容量 s := make([]int, 3, 5) fmt.Println(len(s)) // 3(目前元素數量) fmt.Println(cap(s)) // 5(底層陣列的大小) append:新增元素 nums := []int{1, 2, 3} nums = append(nums, 4) // [1, 2, 3, 4] nums = append(nums, 5, 6, 7) // [1, 2, 3, 4, 5, 6, 7] // 合併兩個 slice more := []int{8, 9} nums = append(nums, more...) // [1, 2, 3, 4, 5, 6, 7, 8, 9] 當 append 超過容量時,Go 會自動分配更大的底層陣列。 ...

2026年2月24日 · 6 min · 1163 words · Jack

Go 語言系列(三):基礎語法

這是 Go 語言從零到 Web 應用系列的第三篇。上一篇我們設定好了開發環境,現在要來學 Go 的基礎語法。 變數宣告 Go 有幾種宣告變數的方式。 var 宣告 // 宣告並指定型別 var name string = "Jack" // 宣告並讓編譯器推斷型別 var age = 30 // 先宣告,之後再賦值(會有零值) var score int // score = 0 var active bool // active = false var label string // label = "" 短變數宣告 := // 只能在函式內使用 name := "Jack" age := 30 pi := 3.14 isReady := true := 是最常用的宣告方式,簡潔直覺。但注意它只能在函式內使用,套件層級的變數必須用 var。 多變數宣告 // 同時宣告多個變數 var x, y, z int = 1, 2, 3 // 短變數宣告也可以 a, b, c := "hello", 42, true // 分組宣告 var ( firstName string = "Jack" lastName string = "Tse" age int = 30 ) 常數 const Pi = 3.14159 const MaxRetries = 3 // 分組宣告 const ( StatusOK = 200 StatusNotFound = 404 StatusError = 500 ) 常數在編譯時就確定值,不能被修改。 ...

2026年2月24日 · 6 min · 1198 words · Jack

Go 語言系列(二):環境設定與 Hello World

這是 Go 語言從零到 Web 應用系列的第二篇。上一篇我們了解了為什麼要學 Go,這篇要動手把環境準備好,並寫出你的第一支 Go 程式。 安裝 Go macOS 使用 Homebrew 是最方便的方式: brew install go 或者從 官方下載頁面 下載 .pkg 安裝檔,雙擊安裝即可。 Windows 從 官方下載頁面 下載 .msi 安裝檔,雙擊後按照精靈指示安裝。安裝程式會自動設定環境變數。 Linux # 下載最新版本(以 1.22 為例) wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz # 解壓到 /usr/local sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz # 加入 PATH(加到 ~/.bashrc 或 ~/.zshrc) export PATH=$PATH:/usr/local/go/bin 驗證安裝 安裝完成後,開啟終端機執行: go version 你應該會看到類似以下的輸出: go version go1.22.0 darwin/arm64 認識 Go 的目錄結構 Go 有幾個重要的環境變數: # 查看 Go 的環境設定 go env 最重要的兩個: ...

2026年2月24日 · 3 min · 557 words · Jack

Go 語言系列(一):為什麼要學 Go?

這是 Go 語言從零到 Web 應用系列的第一篇。整個系列共 10 篇文章,將帶你從完全不懂 Go,到能獨立建構一個具備 CRUD、認證、資料庫的完整 Web 應用程式。 Go 是什麼? Go(又稱 Golang)是 Google 於 2009 年正式推出的開源程式語言,由 Robert Griesemer、Rob Pike 和 Ken Thompson 三位電腦科學大師共同設計。其中 Ken Thompson 是 Unix 和 C 語言的共同發明者,Rob Pike 則是 UTF-8 編碼的共同發明者。 Go 的誕生並非出於學術目的,而是為了解決 Google 內部面臨的真實工程問題: 編譯太慢:大型 C++ 專案的編譯時間動輒數十分鐘甚至數小時 依賴管理混亂:複雜的依賴關係讓專案難以維護 併發程式太難寫:傳統的 thread + lock 模型容易出錯 語言太複雜:C++ 和 Java 的功能越加越多,學習曲線越來越陡 Go 的設計目標很明確:在保持簡單的前提下,解決大規模軟體工程的問題。 Go 的設計哲學 Go 的核心哲學可以用三個字總結:少即是多。 簡潔至上 Go 刻意省略了許多其他語言中「理所當然」的功能: 沒有繼承:用組合(Composition)取代 沒有泛型(Go 1.18 前):直到社群有足夠共識才加入 沒有例外處理:用明確的錯誤回傳值取代 try/catch 沒有 Operator Overloading 沒有隱式型別轉換 這些看似「缺少」的功能,其實是刻意的設計選擇。Go 團隊認為,語言的價值不在於能做多少事,而在於讓開發者能多快理解和維護程式碼。 ...

2026年2月24日 · 2 min · 362 words · Jack