迎接嶄新的一年,一起透過實作簡單的 restful api 來認識 Golang 吧!
什麼是 Golang ? 以下是 Golang 官方文件 上的說明。
Go is an open source programming language supported by Google
Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and networked machines, while its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language.
# install
在 官方網站 安裝完成後,可以透過 $ go version
來確認是否安裝完成以及安裝的版本。
$go version
go version go1.17.5 darwin/amd64
# init project
新增一個檔案夾, example: go-project
,接著在新增的檔案夾 go mod init go-project
這邊完成了初始化 project,並且會產生一個 go.mod
的檔案。
$go mod init go-project
go: creating new go.mod: module go-project
go.mod
檔案會紀錄 所有的 package,舉例來說:安裝了 gorilla/mux
這個 package。
$go get -u github.com/gorilla/mux
go get: added github.com/gorilla/mux v1.8.0
這時進入 go.mod
檔案會看到這邊紀錄了 gorilla/mux
最新的版本。
module go-project
go 1.17
require github.com/gorilla/mux v1.8.0 // indirect
# packages in Go
新增一個 main.go
的檔案來實作。
這邊第一行可以看到使用了 package main
,在不同的檔案中都會定義 package packagename
,而這邊的 main
是執行 go application
的入口點。
package main
接著引入 fmt package
試著在 main function
印出資料。
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, Modules!")
}
在 command line
輸入 go run main.go
就會在 terminal 印出 Hello, Modules!
下一步,先來定義這次會用到的 data 結構。
從下方程式碼中,可以看到我們定義了 Movie struct
包含了 3 個 fields 以及一個 Director struct
。
- 這邊透過
Struct Tags
來定義,讓輸出的資料 id, isbn, title, director 為 Json 中的 key。 *Director
透過宣告一個 pointer 變數讓他的 type 是Director struct
。
package main
import (
"encoding/json"
)
type Movie struct {
ID string `json:"id"`
Isbn string `json:"isbn"`
Title string `json:"title"`
Director *Director `json:"director"`
}
type Director struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
# 實作 main func
- 透過
append function
來新增兩筆假資料進入movies array
。 - 透過
net/http package
中的ListenAndServe function starts an HTTP server
func ListenAndServe(addr string, handler Handler) error
第一個參數接收一個 type 是string
的 addr, 第二個參數接收一個 type 是Handler
的 handler 處理進來的 request。
gorilla/mux package
中mux.Router
會讓進來的 request 對應到正確的 router,並且會去 call 對應到的 route 的 handler。r := mux.NewRouter()
在這邊可以看到:=
代表同時做了宣告和賦值兩件事。
log.Fatal(http.ListenAndServe(":8000", r))
如果有非預期的錯誤出現,在這邊會透過log.Fatal()
印出來。- 透過
r.HandleFunc()
來實作 restful api。
import (
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
type Movie struct {
ID string `json:"id"`
Isbn string `json:"isbn"`
Title string `json:"title"`
Director *Director `json:"director"`
}
type Director struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
var movies []Movie
func main() {
r := mux.NewRouter()
movies = append(movies, Movie{ID: "1", Isbn: "438227", Title: "Emily in Paris", Director: &Director{Firstname: "Michael", Lastname: "Amodio"}})
movies = append(movies, Movie{ID: "2", Isbn: "438228", Title: "The Silent Sea", Director: &Director{Firstname: "Hang-yong", Lastname: "Choi"}})
r.HandleFunc("/movies", getMovies).Methods("GET")
r.HandleFunc("/movies/{id}", getMovie).Methods("GET")
r.HandleFunc("/movies", createMovie).Methods("POST")
r.HandleFunc("/movies/{id}", updateMovie).Methods("PUT")
r.HandleFunc("/movies/{id}", deleteMovie).Methods("DELETE")
fmt.Printf("Starting server at port 8000\n")
log.Fatal(http.ListenAndServe(":8000", r))
}
# 實作 handler func
getMovies func
的 type 是http.HandlerFunc
,在http package
文件上對type HandlerFunc
的介紹是 :an adapter to allow the use of ordinary functions as HTTP handlers
因此在這邊可以使用
ServeHTTP()
的方法,從相關的 source code 可以看到這邊實作的方式相當簡潔。- 透過
encoding/json package
的func NewEncoder(w io.Writer) *Encoder
方法讓 struct 解析成 Json。
- 透過
deleteMovie func
中可以看到使用了gorilla/mux package
的mux.Vars()
方法取得我們在 route 設定的 id。- 在迴圈中透過
range array
,我們可以拿到 array 中的 index 跟 value。 - 這邊的
movies[:index]
透過slices
方法將目標 index 移除。
- 在迴圈中透過
createMovie func
中var movie Movie
這邊宣告了一個 type 是Movie struct
的變數 movie。- 透過
strconv package
中的strconv.Itoa()
方法將 integer 轉成 string,搭配math/rand package
的rand.Intn()
方法產生亂數來製造出 id。
- 透過
package main
import (
"encoding/json"
"fmt"
"log"
"math/rand"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
type Movie struct {
ID string `json:"id"`
Isbn string `json:"isbn"`
Title string `json:"title"`
Director *Director `json:"director"`
}
type Director struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
var movies []Movie
func getMovies(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(movies)
}
func deleteMovie(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
for index, item := range movies {
if item.ID == params["id"] {
movies = append(movies[:index], movies[index+1:]...)
break
}
}
json.NewEncoder(w).Encode((movies))
}
func getMovie(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
for _, item := range movies {
if item.ID == params["id"] {
json.NewEncoder(w).Encode(item)
return
}
}
}
func createMovie(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var movie Movie
_ = json.NewDecoder(r.Body).Decode(&movie)
movie.ID = strconv.Itoa(rand.Intn(1000000))
movies = append(movies, movie)
json.NewEncoder(w).Encode(movie)
}
func updateMovie(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
for index, item := range movies {
if item.ID == params["id"] {
movies = append(movies[:index], movies[index+1:]...)
var movie Movie
_ = json.NewDecoder(r.Body).Decode(&movie)
movie.ID = params["id"]
movies = append(movies, movie)
json.NewEncoder(w).Encode(movie)
return
}
}
}
func main() {
r := mux.NewRouter()
movies = append(movies, Movie{ID: "1", Isbn: "438227", Title: "Emily in Paris", Director: &Director{Firstname: "Michael", Lastname: "Amodio"}})
movies = append(movies, Movie{ID: "2", Isbn: "438228", Title: "The Silent Sea", Director: &Director{Firstname: "Hang-yong", Lastname: "Choi"}})
r.HandleFunc("/movies", getMovies).Methods("GET")
r.HandleFunc("/movies/{id}", getMovie).Methods("GET")
r.HandleFunc("/movies", createMovie).Methods("POST")
r.HandleFunc("/movies/{id}", updateMovie).Methods("PUT")
r.HandleFunc("/movies/{id}", deleteMovie).Methods("DELETE")
fmt.Printf("Starting server at port 8000\n")
log.Fatal(http.ListenAndServe(":8000", r))
}
打開 Insomnia call api 看一下吧!
# 小結
在實作過程中發現有相當多可以延伸與深入學習的地方,整體來說相當有趣!
在閱讀文章時如果有遇到什麼問題,或是有什麼建議,都歡迎留言告訴我,謝謝。😃
# 參考資料
- Document | Writing Web Applications
- Document | http
- Document | Short variable declarations
- golangbot | Arrays and Slices
- digitalocean | How To Use Struct Tags in Go
- Blog | What is Go lang func NewEncoder(w io.Writer) *Encoder?
- Blog | An Introduction to Handlers and Servemuxes in Go
- Blog | Go and JSON
關於作者
喜歡有趣的設計