Nuxt2+TypeScript+CompsitionAPIでApollo

ライブラリのインストール

npm i @nuxtjs/apollo graphql-tag @vue/apollo-composable

plugins の下にapolloのconfigファイルを設置

設定ファイル内で関数が必要なら別ファイルMUSTとなっていたのでいったん別ファイルで用意

import { Context } from '@nuxt/types'
export default (context:Context) => {
  return {
    httpEndpoint: 'GraphQLサーバのエンドポイントURL',

    /*
     * For permanent authentication provide `getAuth` function.
     * The string returned will be used in all requests as authorization header
     */
    // getAuth: () => 'Bearer my-static-token',
  }
}

定義ファイル ( shims-gql.d.ts ) を設置

公式には gql.d.ts とあったのだけど、 shims-vue.d.ts と近しい設定内容にみえたのでファイル名は shims-gql.d.ts としてみた

declare module '*.gql' {
  import { DocumentNode } from 'graphql'

  const content: DocumentNode
  export default content
}

declare module '*.graphql' {
  import { DocumentNode } from 'graphql'

  const content: DocumentNode
  export default content
}

nuxt.config.js に設定追加

  modules: [
    '@nuxtjs/apollo',
  ],

  // Apollo module configuration
  apollo: {
    clientConfigs: {
      default: '~/plugins/ApolloConfig.ts',
    }
  },

  "files": [
    "shims-gql.d.ts",
  ],

グローバル setup() で デフォルトのApolloクライアントインスタンスを登録

plugins/provideApolloClient.ts を設置

import { provide, onGlobalSetup } from '@nuxtjs/composition-api'
import { DefaultApolloClient } from '@vue/apollo-composable'
import { Plugin  } from '@nuxt/types'

const provideApolloClient:Plugin = ({ app }) => {
  onGlobalSetup(() => {
    provide(DefaultApolloClient, app.apolloProvider.defaultClient)
  })
}

export default provideApolloClient

nuxt.config.js に設定追加

  plugins: [
    '~/plugins/provideApolloClient.ts'
  ],

定義ファイルで app.apolloProvider に型付け

#おそらく、@nuxtjs/apollo もしくは nuxt のバージョンが高ければ(最初からあって)不要…?

import { ApolloProvider } from 'vue-apollo/types/apollo-provider'
declare module '@nuxt/types' {
  interface NuxtAppOptions {
    apolloProvider: ApolloProvider
  }
}

HASURAでGraphQLサーバを用意

apolloのconfigファイル書き換え

headerに情報追加が必要なので default を使用しない形に書き換え

import { Context } from '@nuxt/types'
import { setContext } from 'apollo-link-context'
import { from } from 'apollo-link'
import { createHttpLink } from 'apollo-link-http'

export default (context:Context }) => {
  const headersConfig = setContext(() => ({
    headers: {
      'content-type':'application/json',
      'x-hasura-admin-secret': 'たぶんここにベタで書くべきではない気がするが…', 
    },
  }))

  const link = createHttpLink({
    uri: 'GraphQLサーバのエンドポイントURL',
  })

  return {
    link: from([
      headersConfig,
      link
    ]),
    defaultHttpLink: false // これがないとエラーになる
  }
}

gqlファイルを用意

HASURAのコンソール上で作成したGraphQLクエリーを貼り付け

apollo/queries/USER.gql

query USER($id: Int!) {
  user_items_by_pk(id: $id) {
    department
    name
    occupation
    id
    applications {
      id
      free_text
      place
      select
      select_other
      skills(order_by: {skill: asc}) {
        skill
      }
    }
  }
}

apollo/mutations/UPDATE_APPLICATION.gql

#HASURAはオブジェクト(&オブジェクトの配列)引数は objects で指定できた。型もHASURAコンソール上で確認できた。

mutation UPDATE_APPLICATION($id: Int!, $free_text:String!, $place:Int!, $select:Int!, $select_other:String!, $objects: [application_skills_insert_input!]! ) {
  delete_application_skills(where: {application_id: {_eq: $id}}) {
    affected_rows
  }
  insert_application_skills(objects: $objects) {
    returning {
      id
      skill
    }
  }
  update_application_items_by_pk(pk_columns: {id: $id}, _set: {free_text: $free_text, place: $place, select: $select, select_other: $select_other}) {
    free_text
    id
    place
    select
    select_other
    skills(order_by: {skill: asc}) {
      skill
    }
  }

}

store ルートの action で nuxtServerInit をつかって取得

store/index.ts で以下の形で指定
#Context内のapp じゃなくても、this.app でもとれるっぽいな…?

export const actions = actionTree({ state }, {
  async nuxtServerInit ({ commit }, {store, app}:Context) {
    let res = await app.apolloProvider.defaultClient.query<TypeUser>({
      query: USER,
      variables: {
        id: 1,
      },
    });

    const application_id = res.data.user_items_by_pk.applications.length ? res.data.user_items_by_pk.applications[0].id : null;
    const userState = {
      id:res.data.user_items_by_pk.id,
      name:res.data.user_items_by_pk.name,
      department:res.data.user_items_by_pk.department,
      occupation:res.data.user_items_by_pk.occupation,
      application_id
    }
    const applicationValues = application_id ? res.data.user_items_by_pk.applications[0] : null;

    store.dispatch('user/init',userState);
    store.dispatch('application/init',{values:applicationValues});
  }
})

TYPE_USER は以下のような内容で定義
(GraphQLのスキーマから生成できるやり方がありそうだけどそれはまた別途…)

type Skill = {
  skill:number
}

export type Application = {
  id:number
  free_text:string
  place:number
  select:number
  select_other:string
  skills:Skill[]
}

export type TypeUser = {
  user_items_by_pk:{
    department:string
    name:string
    occupation:string
    id:number
    applications:Application[]
  }
}

store module内で mutation

storeモジュール内では this.app で apolloProvider にアクセスできる

import UPDATE_APPLICATION from "@/apollo/mutations/UPDATE_APPLICATION.gql";

export const actions = actionTree({ state, getters, mutations }, {
  update({state,getters,commit}){
    const id = getters.getId;
    const values = getters.getValues;
    const apolloClient = this.app.apolloProvider.defaultClient;
    const variables = {
      id,
      free_text: values.free_text,
      place: values.place,
      select: values.select,
      select_other: values.select_other,
      objects: values.skills? values.skills.map(skill => ({ application_id:id, skill})) : [],
    }
    apolloClient.mutate({
      mutation:UPDATE_APPLICATION,
      variables
    })
  }
}

コンポーネント内でuseQuery

page コンポーネントだったら fetchからとるのが適切なのかもしれない

import {defineComponent} from "@nuxtjs/composition-api";
import { GetApplication } from "@/types";
import { useQuery, useResult } from "@vue/apollo-composable";
import GET_APPLICATION from '@/apollo/queries/GET_APPLICATION.gql';
export default defineComponent({
  setup(props) {
    const { result } = useQuery<GetApplication>(GET_APPLICATION , {id:1});
    const application = useResult(result, null, data => data.application_items_by_pk);
    reutrn {}
  }
}

コンポーネント内でuseMutation

取得後の処理がまだ書けてないけど…

import { defineComponent, useContext } from "@nuxtjs/composition-api";
import { useQuery, useResult, useMutation } from "@vue/apollo-composable";
import UPDATE_APPLICATION from "@/apollo/mutations/UPDATE_APPLICATION.gql";
export default defineComponent({
  setup(props) {
    const { $accessor } = useContext();
    const onBtnClick = (): void => {
      const values = $accessor.application.getValues;
      uptedateApplication(
        {
          id: 1,
           free_text: values.free_text,
          place: values.place,
          select: values.select,
          select_other: values.select_other,
          skill: 1,
        }
      );
    };
   reutrn {onBtnClick}
   }
}