LoginSignup
0
0

More than 1 year has passed since last update.

【Go】APIサーバの構築 ~GraphQL編~

Last updated at Posted at 2022-01-16

はじめに

APIサーバにGraphQLを導入してみたのでメモを残します。

コード全文は以下です。

GraphQL

どこにでも書いてある情報ですが、GraphQLの導入メリットには以下のようなものがあります。

  • エンドポイントが1つでOK(RESTのようなエンドポイントの肥大化を防げる)
  • クエリを用いて必要なデータのみを取ってくることができる(無駄な通信帯域を消費しない)
  • データ型の定義がされていて、クライアント-サーバ間の食い違いを防げる

他にもGraphQL関連の記事を書いているので、もし興味があればご覧ください。

実装

GoでGraphQLを実装するにあたり、以下のライブラリを使用します。

導入

ライブラリをインストールします。

go get github.com/graphql-go/graphql

ルートハンドラーの追加

routes.goにルートハンドラーを追加します。
RESTのときとは違いエンドポイントはこの一つだけでよく、スキーマ定義の追加修正などを行う場合はgraphql.goの中身をさわります。

routes.go
router.HandlerFunc(http.MethodPost, "/v1/graphql", app.moviesGraphQL)

スキーマ定義

graphql.goにスキーマ定義を記述します。

graphql.go
package main

import (
    "backend/models"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "log"
    "net/http"
    "strings"

    "github.com/graphql-go/graphql"
)


var movies []*models.Movie

// スキーマ定義
var fields = graphql.Fields{
    "movie": &graphql.Field{
        Type: movieType,
        Description: "Get movie by id",
        Args: graphql.FieldConfigArgument{
            "id": &graphql.ArgumentConfig{
                Type: graphql.Int,
            },
        },
        Resolve: func(p graphql.ResolveParams) (interface{}, error) {
            id, ok := p.Args["id"].(int)
            if ok {
                for _, movie := range movies {
                    if movie.ID == id {
                        return movie, nil
                    }
                }
            }
            return nil, nil
        },
    },
    "list": &graphql.Field{
        Type: graphql.NewList(movieType),
        Description: "Get all movies",
        Resolve: func(params graphql.ResolveParams) (interface{}, error) {
            return movies, nil
        },
    },
    "search": &graphql.Field{
        Type: graphql.NewList(movieType),
        Description: "Search movies by title",
        Args: graphql.FieldConfigArgument{
            "titleContains": &graphql.ArgumentConfig{
                Type: graphql.String,
            },
        },
        Resolve: func(params graphql.ResolveParams) (interface{}, error) {
            var theList []*models.Movie
            search, ok := params.Args["titleContains"].(string)
            if ok {
                for _, currentMovie := range movies {
                    if strings.Contains(currentMovie.Title, search) {
                        log.Println("Found one")
                        theList = append(theList, currentMovie)
                    }
                }
            }
            return theList, nil
        },
    },
}

var movieType = graphql.NewObject(
    graphql.ObjectConfig{
        Name: "Movie",
        Fields: graphql.Fields {
            "id": &graphql.Field{
                Type: graphql.Int,
            },
            "title": &graphql.Field{
                Type: graphql.String,
            },
            "description": &graphql.Field{
                Type: graphql.String,
            },
            "year": &graphql.Field{
                Type: graphql.Int,
            },
            "release_date": &graphql.Field{
                Type: graphql.DateTime,
            },
            "runtime": &graphql.Field{
                Type: graphql.Int,
            },
            "rating": &graphql.Field{
                Type: graphql.Int,
            },
            "mpaa_rating": &graphql.Field{
                Type: graphql.String,
            },
            "created_at": &graphql.Field{
                Type: graphql.DateTime,
            },
            "updated_at": &graphql.Field{
                Type: graphql.DateTime,
            },
        },
    },
)

func (app *application) moviesGraphQL(w http.ResponseWriter, r * http.Request) {
    // DBから全データを取得する
    movies, _ = app.models.DB.All()

    // リクエストボディを読み込んでクエリをつくる
    q, _ := io.ReadAll(r.Body)
    query := string(q)

    log.Println(query)

    // スキーマをつくる
    rootQuery := graphql.ObjectConfig{Name: "RootQuery", Fields: fields}
    schemaConfig := graphql.SchemaConfig{Query: graphql.NewObject(rootQuery)}
    schema, err := graphql.NewSchema(schemaConfig)
    if err != nil {
        app.errorJSON(w, errors.New("failed to create schema"))
        log.Println(err)
        return
    }

    params := graphql.Params{Schema: schema, RequestString: query}
    resp := graphql.Do(params)
    if len(resp.Errors) > 0 {
        app.errorJSON(w, fmt.Errorf("failed: %+v", resp.Errors))
    }

    j, _ := json.MarshalIndent(resp, "", " ")
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write(j)
}

fieldsmovie list searchのスキーマを定義します。
Typeにはスキーマの型、Argsには引数の型、Resolveにはクエリの結果を記述します。

例えばmovieの場合であれば、Resolve内にArgsで取得したidと一致する結果を返すような処理を記述します。

        Resolve: func(p graphql.ResolveParams) (interface{}, error) {
            id, ok := p.Args["id"].(int)
            if ok {
                for _, movie := range movies {
                    if movie.ID == id {
                        return movie, nil
                    }
                }
            }
            return nil, nil
        },

参考資料

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