0
0
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

oapi-codegenを使ってGoのコード自動生成とAPIサーバーの起動までやってみた

Posted at

はじめに

コードの自動生成をすると、手軽にAPIサーバの起動をすることができました。
備忘録も兼ねて手順を紹介したいと思います。

oapi-codegenのインストール法は別の記事にまとめているので、OpenAPIのyamlファイルとoapi-codegenでGoの自動生成をやってみたを参照してください。

コード全体像Githubリポジトリ

やったこと

yamlファイルの作成

Goおよびoapi-codegenのインストールが終わっているならば、yamlファイルを作成します。
これが仕様書の代わりにもなります。

作成したyamlファイル
openapi.yaml
openapi: "3.0.3"
info:
  version: 0.0.1
  title: REST API
servers:
  - url: http://localhost:9000
tags:
  - name: user
  - name: version
paths:
  /:
    get:
      summary: "バージョン確認"
      operationId: getVersion
      tags:
        - version
      responses:
        "200":
          description: OK
  /register:
    post:
      summary: "新規ユーザー登録"
      operationId: registerUser
      tags:
        - user
      responses:
        "201":
          description: OK
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                  example: sample@test.com
                password:
                  type: string
                  format: password
                  example: Hoge1357
              required:
                - email
                - password
  /user/{firebase_uid}:
    delete:
      summary: "ユーザーの削除"
      operationId: deleteUser
      tags:
        - user
      parameters:
        - name: firebase_uid
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: user {firebase_uid} deleted

APIのエンドポイント、HTTPメソッド、リクエストボディやレスポンスを定義しています。
operationIdプロパティは後で生成するコードの関数名として使用されるので、重複しない名前を考えるようにしましょう。

コード生成

yamlファイルの作成ができれば、コードを生成してみましょう。
自動生成したコードであることがわかるように、generatedディレクトリに保存し、openapi.gen.goというファイル名とします。

自動生成に使用するconfig.yamlも作成しておきます。

config.yaml
package: handler # 任意の名前でOK
output: ./generated/openapi.gen.go # generatedディレクトリが存在しないと失敗する
generate:
  echo-server: true
  models: true
output-options:
  skip-prune: true

公式を真似ただけですが、とりあえずこれで十分です。
generateプロパティをいじることでecho以外のサーバーに指定することができます。

自動生成を行うコマンドは以下のとおりです。

oapi-codegen -config config.yaml openapi.yaml

-config config.yamlは設定ファイルを指定しています。
openapi.yamlの箇所は自動生成に使用するyamlファイルを指定します。

自動生成されるコードは以下に記載しておきます。

生成コード
openapi.gen.go
// Package api provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.16.2 DO NOT EDIT.
package handler

import (
	"fmt"
	"net/http"

	"github.com/labstack/echo/v4"
	"github.com/oapi-codegen/runtime"
	openapi_types "github.com/oapi-codegen/runtime/types"
)

// RegisterUserJSONBody defines parameters for RegisterUser.
type RegisterUserJSONBody struct {
	Email    openapi_types.Email `json:"email"`
	Password string              `json:"password"`
}

// RegisterUserJSONRequestBody defines body for RegisterUser for application/json ContentType.
type RegisterUserJSONRequestBody RegisterUserJSONBody

// ServerInterface represents all server handlers.
type ServerInterface interface {
	// バージョン確認
	// (GET /)
	GetVersion(ctx echo.Context) error
	// 新規ユーザー登録
	// (POST /register)
	RegisterUser(ctx echo.Context) error
	// ユーザーの削除
	// (DELETE /user/{firebase_uid})
	DeleteUser(ctx echo.Context, firebaseUid string) error
}

// ServerInterfaceWrapper converts echo contexts to parameters.
type ServerInterfaceWrapper struct {
	Handler ServerInterface
}

// GetVersion converts echo context to params.
func (w *ServerInterfaceWrapper) GetVersion(ctx echo.Context) error {
	var err error

	// Invoke the callback with all the unmarshaled arguments
	err = w.Handler.GetVersion(ctx)
	return err
}

// RegisterUser converts echo context to params.
func (w *ServerInterfaceWrapper) RegisterUser(ctx echo.Context) error {
	var err error

	// Invoke the callback with all the unmarshaled arguments
	err = w.Handler.RegisterUser(ctx)
	return err
}

// DeleteUser converts echo context to params.
func (w *ServerInterfaceWrapper) DeleteUser(ctx echo.Context) error {
	var err error
	// ------------- Path parameter "firebase_uid" -------------
	var firebaseUid string

	err = runtime.BindStyledParameterWithLocation("simple", false, "firebase_uid", runtime.ParamLocationPath, ctx.Param("firebase_uid"), &firebaseUid)
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter firebase_uid: %s", err))
	}

	// Invoke the callback with all the unmarshaled arguments
	err = w.Handler.DeleteUser(ctx, firebaseUid)
	return err
}

// This is a simple interface which specifies echo.Route addition functions which
// are present on both echo.Echo and echo.Group, since we want to allow using
// either of them for path registration
type EchoRouter interface {
	CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
	DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
	GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
	HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
	OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
	PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
	POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
	PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
	TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
}

// RegisterHandlers adds each server route to the EchoRouter.
func RegisterHandlers(router EchoRouter, si ServerInterface) {
	RegisterHandlersWithBaseURL(router, si, "")
}

// Registers handlers, and prepends BaseURL to the paths, so that the paths
// can be served under a prefix.
func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) {

	wrapper := ServerInterfaceWrapper{
		Handler: si,
	}

	router.GET(baseURL+"/", wrapper.GetVersion)
	router.POST(baseURL+"/register", wrapper.RegisterUser)
	router.DELETE(baseURL+"/user/:firebase_uid", wrapper.DeleteUser)

}

ルーティングを設定してくれていますし、リクエストレスポンスの型も作成してくれています。
パスパラメータが存在するか確認もしてくれているので、後は処理を書くだけで起動ができる状態です。

生成したコードを直接変更すると、再度コード生成を行う際に上書きしてしまう恐れがあります。
なので、実際に使用するファイルにコピペしておきます。
私はhandlerディレクトリを作成し、handler.goとしておきました。

main.goの作成

自動生成したコードにはmain関数がないので作成していきます。

main.go
main.go
package main

import (
	"net/http"

    // 自動生成したコードをインポート
	"Go_Sample_Server/handler"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

type Server struct {}

// handler.goから呼び出される関数の処理を定義
func (h Server) GetVersion(ctx echo.Context) error {
	return ctx.JSON(http.StatusOK, "0.0.1")
}

func (h Server) RegisterUser(ctx echo.Context) error {
	return ctx.JSON(http.StatusCreated, "Success!")
}

func (handle Server) DeleteUser(ctx echo.Context, firebaseUid string) error{
	return ctx.JSON(http.StatusOK, "User deleted")
}

func main(){
	e := echo.New()

	// middleware
	e.Use(middleware.Recover())
	e.Use(middleware.Logger())
    // CORS設定
	e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
		// TODO: Frontのアドレスに変更する
		AllowOrigins: []string{"http://localhost:5173"},
        // 許可するHTTPメソッドを設定
		AllowMethods: []string{http.MethodGet, http.MethodPatch, http.MethodPost, http.MethodDelete, http.MethodOptions},
	}))

	server := Server{}


	handler.RegisterHandlers(e, server)

	e.Logger.Fatal(e.Start("localhost:9000"))
}

go.modの作成/importの調整

go.modファイルを作成します。

go mod init

上記コマンドで作成できるはずですが、上手く作成できない場合はサンプルのgo.modを作成しましょう。
下記のコマンドで作成できます。

go mod init example.com/m/v2
go mod tidy

go.modで指定されるmodule名を確認しておきましょう

go.mod
module Go_Sample_Server

module名はimportに使用します。
任意の名前で良いですが、module名を変更したらmain.goのimportも修正するようにしましょう。

go.main
import(
    "モジュール名/ディレクトリ名"
)

とすることで指定したディレクトリ内のファイルをimportできます。
importしたファイルにある関数を呼び出すときは、importファイルで指定したpackage名.関数名とすればよいです。

APIサーバ起動

go run main.go

上記コマンドで起動できます。
以下の用にEchoの出力が出れば起動しています。

  ____    __
 / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.12.0

後はcurlを叩いたり、フロントから接続したり、openapi.yamlに新たなエンドポイントを作成してみてください。

最後に

コードを自動生成することで、設計やAPIが行う処理に注力することができます。

あくまで、手軽にサーバ起動までできることを紹介したかったので、セキュリティであったり詰めが甘い箇所があると思いますが、ご容赦ください。

今回はGoの自動生成を行いましたが、他の言語も試してみたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0