LoginSignup
10
5

More than 3 years have passed since last update.

Echo+GORMでRESTfulなAPIサーバをつくる

Last updated at Posted at 2020-01-04

Overview

小規模なプロダクトにおいてPythonでAPIサーバはしんどい(できるけどソースが美しくない)ので, 全然使ったことないけどGoでAPIサーバを立ててみたい

Description

普段お仕事ではDjango REST Framework, 趣味ではFlaskを使っているんですが, PythonでORマッパー使おうとするとSQLAlchemyとか使うハメになってしんどいです.

かといってDjangoを使うのはウェイトが重い...
peeweeとかいういい感じのライブラリもあるっぽいですが

ということでせっかくなのでGoで作ってみます

今回は, 要素を一意に特定できるidと文字列textを要素に持つPostを定義して, API経由でCRUD操作を行うようなアプリを作ります

GET: localhost:1323/posts -> 全postの取得
GET: localhost:1323/posts/:id -> id指定でpost取得
PUT: localhost:1323/posts/:id -> id指定でpost更新
POST: localhost:1323/posts/:id -> id指定でpost作成
DELETE: localhost:1323/posts/:id -> id指定でpost削除

Settings

DockerでGoの開発環境を整えます

とりあえず動かしたいという方は後述のソースをまるっとコピりましょう

環境構築について, こちらの記事がわかりやすいです.
Golang(Echo) x docker-composeでホットリロード用いた開発

ディレクトリ構造

.
├── Dockerfile
├── docker-compose.yml
└── src
    ├── main.go
    ├── go.mod
    ├── go.sum
    ├── controller
    │   └── sqlite.go
    └── view
        └── post.go

プログラム本体

ディレクトリ構造に合わせてコピる

main.go

GET, POST, PUT, DELETEなど各メソッドを使える

package main

import (
    "github.com/labstack/echo"
    view "app/view"
)

func main() {
    e := echo.New()
    initRouting(e)
    e.Logger.Fatal(e.Start(":1323"))
}

func initRouting(e *echo.Echo) {
    e.GET("/posts", view.GetAllPosts)
    e.GET("/posts/:id", view.GetPost)
    e.POST("/posts", view.CreatePost)
    e.PUT("/posts/:id", view.UpdatePost)
    e.DELETE("/posts/:id", view.DeletePost)
}

post.go

json:idでレスポンスのjsonのキー名を指定できる. デフォだとPascalCaseになっちゃうのがウザいので設定

gorm:hogeで各属性を指定できる

これDB接続のところDecoratorみたいな感じで綺麗に共通化できないんでしょうか...
詳しい方教えて下さい

package view

import (
    "net/http"
    "github.com/labstack/echo"
    . "app/controller"
)

type Post struct {
    Id int `json:"id" gorm:"primary_key;AUTO_INCREMENT"`
    Text string `json:"text"`
}

func GetAllPosts(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()
    db.AutoMigrate(&Post{})

    var posts []Post
    db.Find(&posts)
    return c.JSON(http.StatusOK, posts)
}

func GetPost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()
    db.AutoMigrate(&Post{})

    if id := c.Param("id"); id != "" {
        var post Post
        db.First(&post, id)
        return c.JSON(http.StatusOK, post)
    } else {
        return c.JSON(http.StatusNotFound, nil)
    }
}

func CreatePost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()
    db.AutoMigrate(&Post{})

    post := new(Post)
    if err := c.Bind(post); err != nil {
        return err
    }
    db.Create(&post)

    return c.JSON(http.StatusOK, post)
}

func UpdatePost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()

    newPost := new(Post)
    if err := c.Bind(newPost); err != nil {
        return err
    }

    if id := c.Param("id"); id != "" {
        var post Post
        db.First(&post, id).Update(newPost)
        return c.JSON(http.StatusOK, post)
    } else {
        return c.JSON(http.StatusNotFound, nil)
    }

}

func DeletePost(c echo.Context) error {
    db := OpenSQLiteConnection()
    defer db.Close()

    if id := c.Param("id"); id != "" {
        var post Post
        db.First(&post, id)
        db.Delete(post)
        return c.JSON(http.StatusOK, post)
    } else {
        return c.JSON(http.StatusNotFound, nil)
    }
}

sqlite.go

hoge.sqlite3はDBを保存したいパスを指定
:memory: の方を使うとインメモリで使える

package controller

import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

func OpenSQLiteConnection() *gorm.DB {
    //db, err := gorm.Open("sqlite3", ":memory:")
    db, err := gorm.Open("sqlite3", "hoge.sqlite3")
    if err != nil {
        panic("failed to connect database.")
    }

    db.LogMode(true)
    return db
}

Docker関連ファイル

libc-dev, gccがないとrealize(ホットリロード用ライブラリ)がインストールできないのでインスコ

Dockerfile

FROM golang:1.13.5-alpine as build

WORKDIR /go/app

COPY src .

RUN apk add --no-cache git gcc libc-dev \
 && go build -o app \
 && go get gopkg.in/urfave/cli.v2@master \
 && go get github.com/oxequa/realize

FROM alpine

WORKDIR /app

COPY --from=build /go/app/app .

RUN addgroup go \
  && adduser -D -G go go \
  && chown -R go:go /app/app

CMD ["./app"]

docker-compose.yml

version: '3.5'

services:
  app:
    build:
      context: .
      target: build
    volumes:
      - ./src:/go/app
    command: realize start --run --no-config
    ports:
      - 1323:1323

Run

モジュール管理用ファイルのgo.modを生成

$ docker run --rm -v `pwd`:/go/app -w /go/app golang:1.13.5-alpine go mod init app

各ファイルを作り終わったら次のコマンドでイメージのビルド&実行

$ docker-compose build
$ docker-compose up

~
app_1  | [02:18:27][APP] : Install started
app_1  | [02:18:28][APP] : Install completed in 0.446 s
app_1  | [02:18:28][APP] : Running..
app_1  | [02:18:28][APP] :    ____    __
app_1  | [02:18:28][APP] :   / __/___/ /  ___
app_1  | [02:18:28][APP] :  / _// __/ _ \/ _ \
app_1  | [02:18:28][APP] : /___/\__/_//_/\___/ v3.3.10-dev
app_1  | [02:18:28][APP] : High performance, minimalist Go web framework
app_1  | [02:18:28][APP] : https://echo.labstack.com
app_1  | [02:18:28][APP] : ____________________________________O/_______
app_1  | [02:18:28][APP] :                                     O\
app_1  | [02:18:28][APP] : ⇨ http server started on [::]:1323

コンテナができたらプログラムが実行されます.
ソースコードを変更すると自動で更新されるのがいい感じです!

各メソッドが使えるかも試しておきましょう
curlでもいいですが, 直感的に使えるhttpieが便利です

# 全件取得(まだ0件)
$ http GET localhost:1323/posts

# 新規作成(idは自動インクリメント)
$ http POST localhost:1323/posts text=hoge
$ http GET localhost:1323/posts

# id=1のtextを更新
$ http PUT localhost:1323/posts/1 text=fuga
$ http GET localhost:1323/posts/1

# id=1を削除
$ http DELETE localhost:1323/posts/1
$ http GET localhost:1323/posts

Tips

go getなどコマンドを打ちたいときは

$ docker ps

で実行中のコンテナIDを調べて,

$ docker exec -it {CONTAINER_ID} sh

でコンテナ内に入れます

References

https://github.com/labstack/echo
https://github.com/jinzhu/gorm
Golang(Echo) x docker-composeでホットリロード用いた開発

10
5
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
10
5