[Advent Calendar 2021][Day22]カレンダーの更新削除を実装する(2)

undefined

はじめに

Advent Calendar 2021のDay22。 今回もカレンダーの更新削除を実装する続きです。

GraphQL Client

GoでHasuraGraphQLを呼び出すので、GraphQL Clientを選びます。 ピックアップした候補は3つです。

自動生成はしてくれないので、型を自分で作る必要がある。 呼び出しは簡単にできるので学習コストは低い。

自動生成できる。使ったことあり。

自動生成できる。使ったことはない。

Nuxt側と同様に自動生成できるライブラリが良いなと思います。今回は、まだ使ったことのないgqlgencを使おうと思います。

gqlgenc setup

go install github.com/Yamashou/gqlgencgo get github.com/Yamashou/gqlgencgo get github.com/99designs/gqlgen

gqlgencの設定ファイルを記述します。

  • .gqlgenc.yml
model:  filename: ./gen/models_gen.goclient:  filename: ./gen/client.gomodels:  Int:    model: github.com/99designs/gqlgen/graphql.Int64  Date:    model: github.com/99designs/gqlgen/graphql.Timeendpoint:  url: http://localhost:8080/v1/graphql  headers:    x-hasura-admin-secret: myadminsecretkeyquery:  - "./query/*.graphql"

Mutationを定義します。Actionが実行された時に下記のMutationをHasuraに送信します。

  • query/mutation.graphql
mutation InsertCalendarMutation($name: String!) {  insert_calendars_one(object: {name: $name}) {    id    name    uid    created_at    updated_at  }}mutation UpdateCalendarMutation($id: Int!, $name: String = "") {  update_calendars_by_pk(pk_columns: {id: $id}, _set: {name: $name}) {    uid    name    id    created_at    updated_at  }}

コード生成してきます。指定したフォルダにコードが生成されればOKです。

gqlgenc

gqlgencで生成されたGraphQL Clientを使う

graphqlのクエリは、HasuraからAuthorizationヘッダを送ってもらって、そのヘッダをそのままつけて送ります。そうすることで、認可ユーザとしてHasuraを使うことができます。Admin権限はできるだけ使わないほうがセキュリティ的によいかと思います。

  • gqlclient.go
package mainimport (    "fmt"    "net/http"    "github.com/cloudandbuild/Advent-Calendar-2021/goapp/gen")func GetGraphqlClient(token string) *gen.Client {    baseURL := getenv("HASURA_BASE_URL", "http://localhost:8080/v1/graphql")    return NewGraphqlClient(        &GraphqlClientComponent{},        &GraphqlClientConfig{            BearerToken: token,            BaseURL:     baseURL,        },    )}type GraphqlClientComponent struct {}type GraphqlClientConfig struct {    BearerToken string    BaseURL     string}func NewGraphqlClient(c *GraphqlClientComponent, cfg *GraphqlClientConfig) *gen.Client {    authHeader := func(req *http.Request) {        req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cfg.BearerToken))    }    return gen.NewClient(http.DefaultClient, cfg.BaseURL, authHeader)}

記事のタイトルは、更新削除とありますが、作成部分もActionに置き換えてバリデーションをしたほうが良いので、今回は試しに作成部分を置き換えていきます。

  • handler.goのSwitch部分
    switch actionPayload.Action.Name {    case "actionCreateCalendar":        args := actionCreateCalendarArgs{}        err := mapstructure.Decode(actionPayload.Input, &args)        if err != nil {            break        }        err = args.Validate()        if err != nil {            break        }        result, err = actionCreateCalendar(ctx, args, token)

型定義はがっつり変更しています。Hasuraから送られてくるInput情報を生成されたものに変えて、Validationの定義を追加しました。Hasura Actionを使わない場合は、こまかなビジネスロジックやバリデーションを行うことが出来ませんが、Actionだとできるようになります。

  • types.go
package mainimport (    "github.com/cloudandbuild/Advent-Calendar-2021/goapp/gen"    validation "github.com/go-ozzo/ozzo-validation")type actionCreateCalendarArgs struct {    In gen.ActionCreateCalendarInput `json:"in"`}func (r *actionCreateCalendarArgs) Validate() error {    p := &r.In    return validation.ValidateStruct(p,        validation.Field(            &p.Name,            validation.Length(1, 20),        ),    )}

変換する部分があるので、つらつらと書いていますが、InsertCalendarMutationを呼び出すだけのシンプルなコードです。

  • handler.goのアクション実行部分
func actionCreateCalendar(ctx context.Context, args actionCreateCalendarArgs, token string) (response *gen.ActionCreateCalendarOutput, err error) {
    cli := GetGraphqlClient(token)
    res, err := cli.InsertCalendarMutation(context.Background(), *args.In.Name)
    if err != nil {
        return response, err
    }
    return &gen.ActionCreateCalendarOutput{
        ID:        &res.InsertCalendarsOne.ID,
        Name:      &res.InsertCalendarsOne.Name,
        UID:       &res.InsertCalendarsOne.UID,
        CreatedAt: mustParseTime(res.InsertCalendarsOne.CreatedAt),
        UpdatedAt: mustParseTime(res.InsertCalendarsOne.UpdatedAt),
    }, nil
}

nuxt側のInsertMutationをActionに置き換える。

mutation ActionCreateCalendarMutation($name: String = "") {  actionCreateCalendar(in: {name: $name}) {    created_at    id    name    uid    updated_at  }}

コード生成します。

yarn graphql-codegen

差分だと分かりづらいのでScript部分の全コード。urqlに渡すMutation定義をActionに置き換えています。もともとエラーハンドリング出来ていなかったのですが、追加してみました。サーバーサイドで出力されたバリデーションエラーのメッセージを表示に使うようできます。

  • pages/index.vue
<script setup lang="ts">import {    ListCalendarsQueryDocument,    ActionCreateCalendarMutationDocument,    ActionCreateCalendarMutationMutationVariables} from "~/gql/queries/calendar";import { useClientHandle } from "@urql/vue";import { useNuxtApp } from '#app'const urql = useClientHandle();const queryResult = await urql.useQuery({ query: ListCalendarsQueryDocument })const { data, fetching } = await queryResultconst nuxtApp = useNuxtApp()const user = useState('user')const errorMessage = ref('')const calendarName = ref('')const createCalendarResult = urql.useMutation(ActionCreateCalendarMutationDocument);const insert = async () => {    const variables: ActionCreateCalendarMutationMutationVariables = {        name: calendarName.value    };    const { error } = await createCalendarResult.executeMutation(variables)    return error}const openModal = ref(false)const onOkClick = async () => {    const error = await insert()    if (error) {        console.error(error)        errorMessage.value = error.message        return    }    await queryResult.executeQuery({        requestPolicy: 'network-only',    })    openModal.value = false}</script>

まとめ

gqlgencはこれまで触ってきたGraphQLクライアントと比べてかなり使いやすい印象です。 次回も、カレンダーの更新削除を実装する続きをしていきます。