Go初心者向けに始めた記事の第5回になります。
今回はGoでどのようにAPIを使っていくかを紹介します。
基本的にAPIの開発をする場合は、RubyはRuby on Rails、PHPはLaravelなどAPIを開発する場合、フレームワークを使用すると思いますが、自分の働いている会社ではGoのAPI開発でフレームワークは使用しません。理由を含め説明していきます。
フレームワークをしない理由
理由として、net/httpというhttpクライアントとサーバーの実装ができる標準のライブラリが優秀だからです。net/httpがあればサーバーをすぐに作れるので、あとは足りてない部分を他のライブラリを導入するのが効果的です。
httpサーバーの実装
インストール
HTTPルーターを簡単に実装できるgo-chi、リクエストを構造体に変換してくれるbindingのライブラリをインストールします。
go get -u github.com/go-chi/chi
go get -u github.com/mholt/binding
コード
例として、ユーザーの作成と詳細取得のAPIを作成してみました。
今回はDBに接続しないので、レスポンスの内容はリクエストの内容と固定値を返すようにしてます。
cmd/root.go
前回からの差分として、サーバーの設定と起動を追加しています。
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"log"
"net/http"
"os"
"github.com/SND1231/go-column/router"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type Config struct {
Type string
Host string
Port int
User string
Password string
Name string
}
var config Config
var rootCmd = &cobra.Command{
Use: "go-column",
Run: func(cmd *cobra.Command, args []string) {
// configの中身を出力
log.Printf("configの中身:{type: %s, host: %s, port: %d, user: %s, pass: %s, name: %s}",
config.Type, config.Host, config.Port, config.User, config.Password, config.Name)
// サーバーの設定
r := router.Get()
srv := &http.Server{
Addr: ":3020",
Handler: r,
}
// サーバーの起動
srv.ListenAndServe()
},
}
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
// 初期化処理
// configの設定
rootCmd.Flags().StringP("configName", "n", "default.toml", "config file name")
// Runを実行するたびに、initConfigを呼び出す。その後に、Runの処理が動き出す。
cobra.OnInitialize(initConfig)
}
func initConfig() {
configName, _ := rootCmd.Flags().GetString("configName")
viper.SetConfigFile(configName)
// 設定ファイルを読み込む
if err := viper.ReadInConfig(); err != nil {
log.Println(err)
os.Exit(1)
}
// 設定ファイルの内容を構造体に設定
if err := viper.Unmarshal(&config); err != nil {
log.Println(err)
os.Exit(1)
}
}
router/router.go
ここではAPIを実装しているhandlerの初期化をして、どのURLが来た時に、どの処理を呼び出すかルーティングをしている。
package router
import (
"github.com/SND1231/go-column/handler"
"github.com/go-chi/chi"
)
func Get() *chi.Mux {
r := chi.NewRouter()
userHandler := handler.NewUserHandler()
r.Route("/user", func(r chi.Router) {
r.Post("/add", userHandler.Add)
r.Get("/detail", userHandler.Get)
})
return r
}
handler/user.go
ユーザーの作成、詳細取得のAPIの実装はここでしている。
処理として、リクエストからリクエスト用の構造体に変換して、その値を使ってレスポンス用の構造体を作成してレスポンスを返すようにしている。
package handler
import (
"encoding/json"
"log"
"net/http"
"github.com/mholt/binding"
)
type UserHandler struct{}
func NewUserHandler() *UserHandler {
return &UserHandler{}
}
// 失敗時のレスポンスの設定
func setErrorResponse(w http.ResponseWriter, status int) {
// レスポンスのヘッダ設定
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// 引数のステータス設定
w.WriteHeader(status)
}
// 成功時のレスポンスの設定
func setSuccessResponse(w http.ResponseWriter, res []byte) {
// レスポンスの内容をjsonに変換
w.Write(res)
}
// ユーザーの作成APIのリクエスト
type AddUserInput struct {
Name string
Age int
}
// リクエストのマッピング。ポインターレシーバーにすること
func (input *AddUserInput) FieldMap(r *http.Request) binding.FieldMap {
return binding.FieldMap{
&input.Name: "name",
&input.Age: "age",
}
}
// ユーザーの作成APIのレスポンス
type AddUserOutput struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
// ユーザーの作成API
func (h *UserHandler) Add(w http.ResponseWriter, r *http.Request) {
var err error
var berr binding.Errors
var response *AddUserOutput
var res []byte
// request -> AddUserInput型に変換
var request AddUserInput
berr = binding.Bind(r, &request)
if berr != nil {
log.Println(berr)
setErrorResponse(w, http.StatusInternalServerError)
return
}
// responseの作成
response = &AddUserOutput{
ID: 1,
Name: request.Name,
Age: request.Age,
}
// レスポンスをjsonに変換
res, err = json.Marshal(response)
if err != nil {
log.Println(err)
setErrorResponse(w, http.StatusInternalServerError)
return
}
setSuccessResponse(w, res)
}
// ユーザーの詳細取得APIのリクエスト
type GetUserInput struct {
ID int
}
// リクエストのマッピング。ポインターレシーバーにすること
func (input *GetUserInput) FieldMap(r *http.Request) binding.FieldMap {
return binding.FieldMap{
&input.ID: "id",
}
}
// ユーザーの詳細取得APIのレスポンス
type GetUserOutput struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
// ユーザーの詳細取得API
func (h *UserHandler) Get(w http.ResponseWriter, r *http.Request) {
var err error
var berr binding.Errors
var response *GetUserOutput
var res []byte
// request -> GetUserInput型に変換
var request GetUserInput
berr = binding.Bind(r, &request)
if berr != nil {
log.Println(berr)
setErrorResponse(w, http.StatusInternalServerError)
return
}
// responseの作成
response = &GetUserOutput{
ID: request.ID,
Name: "Jony",
Age: 45,
}
// レスポンスをjsonに変換
res, err = json.Marshal(response)
if err != nil {
log.Println(err)
setErrorResponse(w, http.StatusInternalServerError)
return
}
setSuccessResponse(w, res)
}
最終的には以下のようなリポジトリになります。
実行結果
今回APIの実行確認でVScodeのREST Client拡張を使用する。
VScodeの拡張機能から以下のREST Client(以下の写真)をインストールする。
使い方は以下を参照
POST http://localhost:3020/user/addの実行結果
HTTP/1.1 200 OK
Date: Wed, 31 May 2023 14:07:53 GMT
Content-Length: 31
Content-Type: text/plain; charset=utf-8
Connection: close
{
"id": 1,
"name": "taro",
"age": 18
}
GET http://localhost:3020/user/detail?id=1500の実行結果
HTTP/1.1 200 OK
Date: Wed, 31 May 2023 14:07:37 GMT
Content-Length: 34
Content-Type: text/plain; charset=utf-8
Connection: close
{
"id": 1500,
"name": "Jony",
"age": 45
}
まとめ
今回はAPIの作成について説明をしてきました。
次はDBと接続する方法について解説をしていきたいと思います。
今回の記事以外にもGoについて書いた記事がございますので、もし興味がありましたら、おすすめ記事も見てもらえたらと思います。
【おすすめ記事のリンク】
コメント