この記事は、以前Qiitaに投稿した「RESTAPI WITH Go言語!」の改造版になります。
DBを一切使わない単純なAPIです。
実装方法を変えただけで、処理内容については変更なしです。
処理内容の詳細は、Qiita記事の方をご覧ください。
<目次>
記事まとめ
Install Package
次のモジュールをインストールしてください。
1 2 3 |
go get -u github.com/gorilla/mux go get github.com/subosito/gotenv go get github.com/lib/pq |
プロジェクト作成
続いて、プロジェクトを作成します。
1 2 3 4 5 6 7 8 9 |
make golang_restapi cd golang_restapi mkdir article touch article/article.go mkdir article_server touch article_server/article_server.go touch article_server/main.go mkdir utils touch utils/json.go |
Articleの実装
Article(記事)モデルをstructで定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
// article/article.go package article import "encoding/json" // Article is a model to express article. type Article struct { id int title string author string postdate string } // NewArticle creates a new article func NewArticle(id int, title, author, postdate string) *Article { return &Article{id, title, author, postdate} } // ID returns id func (a *Article) ID() int { return a.id } // Title returns title func (a *Article) Title() string { return a.title } // Author returns author func (a *Article) Author() string { return a.author } // Postdate returns postdate func (a *Article) Postdate() string { return a.postdate } // MarshalJSON return bytes func (a *Article) MarshalJSON() ([]byte, error) { return json.Marshal(struct { ID int `json:"id"` Title string `json:"title"` Author string `json:"author"` PostDate string `json:"postdate"` }{ ID: a.id, Title: a.title, Author: a.author, PostDate: a.postdate, }) } // UnmarshalJSON exchanges byte data to Article struct func (a *Article) UnmarshalJSON(data []byte) error { v := &struct { ID *int `json:"id"` Title *string `json:"title"` Author *string `json:"author"` PostDate *string `json:"postdate"` }{ ID: &a.id, Title: &a.title, Author: &a.author, PostDate: &a.postdate, } if err := json.Unmarshal(data, &v); err != nil { return err } return nil } |
ネットワーク越しのデータ変換(Json <-> Go Struct)には、jsonパッケージのMarshalJSON, UnMarshalJSONを用います。
MarshalJSONは、○○データをArticleStructに変換(Encode)。
UnMarshalJSONは、JSONデータを○○データに変換(Decode)。
Article Structのフィールドは小文字(idなど)になっており、外部参照ができません。このままだとネットワーク越しのデータ通信ができないため(空文字が渡される)、Marshal/UnMarshalで再定義しています。※Example (CustomMarshalJSON)
ちなみに、UnmarshalJSONは、既存の処理をオーバーライドするため、この名前(UnmarshalJSON)でなければなりません。※MarshalJSONも同様
そして、UnmarshalJSONを設定すると、他の処理から呼び出さなくても、メソッド内の処理が適用されます。※すべての実装が終わったら、このメソッドをコメントアウトしてみてください。Insert処理でデータが追加できなくなるはずです
Responseメッセージの作成
Marshalは、次の使い方も可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// utils/json.go package utils import "encoding/json" // JSONStatus retuns bytes func JSONStatus(message string) []byte { m, _ := json.Marshal(struct { Message string `json:"message"` }{ Message: message, }) return m } |
この処理によりReponseとして返したいメッセージ内容をカスタマイズできます。
上記の場合だと、「{Message: “fail”}」の形式でメッセージを呼び出し元に返せます。
Article Serverの作成
Article Serverを立てます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
// article_server/article_server.go package main import ( "encoding/json" "fmt" "io" "log" "net/http" "strconv" "github.com/gorilla/mux" "github.com/hoge/golang_restapi/article" "github.com/hoge/golang_restapi/utils" ) // ArticleServer struct type ArticleServer struct { router *mux.Router port uint16 articles []article.Article } // NewArticleServer creates self func NewArticleServer(router *mux.Router, port uint16) *ArticleServer { as := &ArticleServer{} as.router = router as.port = port return as } // GetRouter returns router func (as *ArticleServer) GetRouter() *mux.Router { return as.router } // CreateArticle returns article func (as *ArticleServer) CreateArticle(id int, title, author, post string) *article.Article { return article.NewArticle(id, title, author, post) } // GetArticles pulls all data func (as *ArticleServer) GetArticles(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(as.articles) } // GetArticle pulls a data func (as *ArticleServer) GetArticle(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) i, _ := strconv.Atoi(params["id"]) ok := false for _, article := range as.articles { if article.ID() == i { ok = true json.NewEncoder(w).Encode(&article) } } if !ok { io.WriteString(w, string(utils.JSONStatus("NO ARTICLE!!!"))) } } // AddArticle creates a new data and add it to database func (as *ArticleServer) AddArticle(w http.ResponseWriter, r *http.Request) { var article article.Article json.NewDecoder(r.Body).Decode(&article) as.articles = append(as.articles, article) json.NewEncoder(w).Encode(as.articles) } // UpdateArticle updates the data of the database func (as *ArticleServer) UpdateArticle(w http.ResponseWriter, r *http.Request) { var article article.Article json.NewDecoder(r.Body).Decode(&article) for i, item := range as.articles { if item.ID() == article.ID() { as.articles[i] = article } } json.NewEncoder(w).Encode(as.articles) } // RemoveArticle removes a data you want to delete from database func (as *ArticleServer) RemoveArticle(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id, _ := strconv.Atoi(params["id"]) for i, item := range as.articles { if item.ID() == id { as.articles = append(as.articles[:i], as.articles[i+1:]...) } } json.NewEncoder(w).Encode(as.articles) } // Run registers endpoint to access url func (as *ArticleServer) Run() { as.router.HandleFunc("/articles", as.GetArticles).Methods("GET") as.router.HandleFunc("/articles/{id}", as.GetArticle).Methods("GET") as.router.HandleFunc("/articles", as.AddArticle).Methods("POST") as.router.HandleFunc("/articles", as.UpdateArticle).Methods("PUT") as.router.HandleFunc("/articles/{id}", as.RemoveArticle).Methods("DELETE") log.Println("Listen server ....") port := fmt.Sprintf(":" + strconv.Itoa(int(as.port))) log.Fatal(http.ListenAndServe(port, as.router)) } |
ArticeServer Structをメソッドとして定義しました。
このようにすれば、結構わかりやすく実装できますね。
※上記の処理内容については、Qiitaをご覧ください。
Main関数の実装
最後にMain関数を実装して終了です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package main import ( "github.com/gorilla/mux" ) var as *ArticleServer func init() { router := mux.NewRouter() as = NewArticleServer(router, 8080) for i := 1; i <= 5; i++ { article := as.CreateArticle(i, "Harry potter", "J.K", "2019/10/29") as.articles = append(as.articles, *article) } } func main() { as.Run() } |
init関数内でデータを作成し、main関数内のRunメソッドにてArticle Serverを起動しています。
動作確認
この記事を参考に、動作確認をしてみましょう。
Article Serverの起動方法は、以下です。
1 2 3 |
$ go run article_server/*.go 2019/11/17 08:50:20 Listen server .... |
PostmanやTalen API Testerなどを使って動作確認をしてみてください。
URLの一覧も記載しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# url 一覧 # GET localhost:8080/articles localhost:8080/articles/{id} # idには、1, 2, 3... などの数値が入る # POST localhost:8080/articles # データ {'id': 1, 'title': 'Harry Potter', 'author': 'J.K', 'postdata': '2002/12/21'} # PUT localhost:8080/articles # データ {'id': 1, 'title': 'Harry Potter2', 'author': 'J.K', 'postdata': '2004/12/21'} # DELETE localhost:8080/articles/{id} # idには、1, 2, 3... などの数値が入る |
おわりに
Go言語の実装にも慣れて来ました。Go言語を使ったシステム開発に携わってみたいですね^^
コメントを残す
コメントを投稿するにはログインしてください。