nuxt3
vue
graphql
2023.02.05
エジプトからVue3.jsのNuxt3とgraphqlで開発を解説してみます。
こんにちは。
今エジプトのナイル川の中洲のソフィテルホテルにいます。
前回思った以上にnuxt3の記事の反響があったので、
実際の開発をもう少し書かないとやばいなと思いまして、
前のブログの続きを書こうと思います。
こちらのブログ見る前に
こちらのvue3 nuxt3 graphqlで実装経験のある方のブログ
「Nuxt 3 のベータ版が安定版に突入!Nuxt 3 実装経験のあるエンジニアが最新の Nuxt 3 アップデート内容をまとめます」
を見た方が用語等でわかりやすいかもです。
(弊社はwebエンジニアの転職におすすめな転職サイトを紹介しています。)
まずいつもの運営から何を話してもいいと言われている介護のパークのリニューアルの実例を参考に解説していきます。
docker-compose.ymlはこんな感じになります。
version: "3"
volumes:
php-fpm-socket:
pgdata:
services:
postgres:
image: postgres:13.4
ports:
- 5432:5432
volumes:
- pgdata:/var/lib/postgresql/data:delegated
environment:
POSTGRES_USER: kaigo_dev
POSTGRES_PASSWORD: kaigo_dev
PGPASSWORD: kaigo
POSTGRES_DB: kaigo_kyujin_park_dev
TZ: "Asia/Tokyo"
pgadmin4:
image: dpage/pgadmin4:6.11
ports:
- 8888:80
volumes:
- ./db/pgadmin4:/var/lib/pgadmin:delegated
environment: # Set the login information of pgAdmin 4.
PGADMIN_DEFAULT_EMAIL: root@root.com
PGADMIN_DEFAULT_PASSWORD: root
hostname: pgadmin4
container_name: pgadmin4
depends_on:
- postgres
restart: always
php:
build: ./php
volumes:
- ./apps:/var/www/html:delegated
- ./php/php.ini:/usr/local/etc/php/conf.d/php.ini
- ./php/php-fpm.d/zzz-docker.conf:/usr/local/etc/php-fpm.d/zzz-docker.conf
- type: volume
source: php-fpm-socket
target: /var/run/php-fpm
volume:
nocopy: true
environment:
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: kaigo_kyujin_park_dev
DB_USERNAME: kaigo_dev
DB_PASSWORD: kaigo_dev
depends_on:
- postgres
- mailhog
nginx:
image: nginx:latest
volumes:
- ./nginx/conf.d/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf
- ./apps:/var/www/html
- type: volume
source: php-fpm-socket
target: /var/run/php-fpm
volume:
nocopy: true
# restart: always
ports: ["80:80"]
depends_on: ["php"]
mailhog:
image: mailhog/mailhog:latest
ports:
- "1025:1025"
- "8025:8025"
networks:
- default
redis:
image: redis:latest
volumes:
- ./redis/data:/data
ports:
- 6379:6379
バックエンドの構築をしていきます。
■ dockerコンテナの立ち上げ
$ docker-compose up -d
■ composer install
$ docker-compose exec php composer install
■ Storage
$ docker-compose exec php php artisan storage:link
■ .envの作成
$ cd app && cp .env.example .env
■ キー発行
$ docker-compose exec php php artisan key:generate
■ postgresへデータをダンプ
// SQLファイルが圧縮されている場合は下記を実行して解凍
$ gunzip kaigo_kyujin_park.sql.gz
$ docker-compose exec -T postgres pg_restore -U kaigo_dev -d kaigo_kyujin_park_dev < kaigo_kyujin_park.sql
■ /etc/hostの編集
// 下記を追記
127.0.0.1 local.5159289.jp
DEV コマンド
migration 実行
$ docker-compose exec php php artisan migrate
migrationファイル作成
$ docker-compose exec php php artisan make:migration xxxxxx
開発環境URL
Host
local.5159289.jp
Admin
ttp://local.5159289.jp/admin
Graphql Play Ground
ttp://local.5159289.jp/graphiql
はい。ここまでは良いですね。
私も市場を通ってピラミッドに移動します。
そしてフロントエンドを構築していきます。
Setup
#graphQL入れます $ yarn -D add @graphql-codegen/cli @graphql-codegen/typescript-operations @graphql-codegen/typescript-vue-apollo @vue/apollo-composable graphql typescript @graphql-codegen/typescript @graphql-codegen/typescript-graphql-request $ yarn -D add @nuxtjs/apollo@next # yarn $ yarn install # npm $ npm install # pnpm $ pnpm install
Development Server
http://localhost:3000
yarn devを実行
これで準備はできましたね
ではまずは、先ほどyarn -D addのコマンドで、graphqlのライブラリをインストールしたので、使ってみましょう。
設定を作っていきます。
設定ファイルはcodegen.ymlの中に書いていきます。
(このymlは自分で作るものです)
このファイブラリはgraphqlの記述があるコードから、
laravel側へqueryを投げるための関数を生成する役割です。
■schema とは graphqlのAPIのエンドポイントを書きます。
■documentsとはgraphqlファイルが置かれているパスを記載します。
■generatesはgraphql code genで生成されたコードをどこに設置するかです。
ではdocumentsの中にgraphqlの記述を書いていきましょう。
このファイルのコードは、
query Jobs(
$client_id: ID
$prefecture_ids: [ID]
$city_ids: [ID]
$employment_type_codes: [ID]
$merit_codes: [ID]
$job_type_small_codes: [ID]
) {
Jobs(
prefecture_ids: $prefecture_ids
city_ids: $city_ids
client_id: $client_id
employment_type_codes: $employment_type_codes
merit_codes: $merit_codes
job_type_small_codes: $job_type_small_codes
) {
data {
id
job_pr
client {
id
name
url
policy
advantage
eudcation_human_resources
}
prefecture {
id
name
}
distriction {
id
name
}
meritCodes {
code
merit_category_code
name
merit_display_name
meritCategoryCode {
code
name
}
}
jobTypeSmallCode {
code
name
}
employmentTypeCode {
code
name
}
job_pict_0
job_pict_1
job_pict_2
job_pict_3
job_pict_4
access
transportation_fee
content
training_description
experience
qualification
required_personality
suitable_personality
member_features
atmosphere
nursery_advantage
advantage
map_url
salary_description
working_description
earlytime_working
daytime_working
latetime_working
nighttime_working
working_time_description
welfare
holidays
selection_flow
valid_chk
}
}
}
query Job($id: ID!) {
Job(id: $id) {
id
job_pr
client {
id
name
url
policy
advantage
eudcation_human_resources
}
prefecture {
id
name
}
distriction {
id
name
}
meritCodes {
code
merit_category_code
name
merit_display_name
meritCategoryCode {
code
name
}
}
jobTypeSmallCode {
code
name
}
employmentTypeCode {
code
name
}
job_pict_0
job_pict_1
job_pict_2
job_pict_3
job_pict_4
access
transportation_fee
content
training_description
experience
qualification
required_personality
suitable_personality
member_features
atmosphere
nursery_advantage
advantage
map_url
salary_description
working_description
earlytime_working
daytime_working
latetime_working
nighttime_working
working_time_description
welfare
holidays
selection_flow
valid_chk
}
}
この長いコードの解説をします。
ただその前に、
ピラミッドとスフィンクスの横で休憩してから解説します。
はい。
では先ほどのコード、
冷静にみると、
query Jobs( ■変数名■) {
Jobs(
■where句■
) {
■select文■
}
}
という形になっています。
■変数名■のところは
$client_id: ID
$prefecture_ids: [ID]
とありますが、
これはこれからwhere句やselect文でこのような変数を使っていくよということと
その型を設定しています。[ID]ならIDが配列で来ることを示しています。
そして、Jobs(■where句■)のところは
prefecture_ids: $prefecture_ids
city_ids: $city_ids
と、ありますが、これはそぞれのカラムに値をセットしています。
■select■のところは、
data {
id
job_pr
client {
id
name
url
policy
advantage
eudcation_human_resources
}
}
のように書いてありますが、
これは普通に取得するカラムを指定しているだけです。
ページネーション情報等はdataの中のid等と同階層に持たせる予定です。
このようにしてgraphqlをnuxt3に書いたとします。
今回graphqlクライアントとしてapolloを使います。
(axiosのようなものだと思ってください)
apolloに関してはこのブログが多分私のブログよりも詳しいです(まだ見てない)
apolloを使えるようにするためにも設定をまた記載します。
apollo: {
clients: {
default: {
httpEndpoint:
process.env.GRAPHQL_END_OPOINT || 'http://local.5159289.jp/graphql',
httpLinkOptions: {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
},
},
},
},
nuxt.config.tsにこれを追記するとapolloが使えるようになります。
わかりますね。
では早朝のクフ王の墓の上でmacを広げて続きをやっていきます。
これから、graphql code genとapolloを使って関数を作っていきます。
どうやって作るかというと、
ターミナルでfrontendファイルのルートで、
$ yarn graphql-codegen
を実行します。
そうすると、graphqlのファイルを読み込んで
サーバーサイドのAPIと一回通信して、通信時にそれぞれの変数に入ってきた情報の型を自動的にみてくれて、
型情報も定義された関数をgeneratedフォルダに作ってくれます。
はい、ここにできてきます。
ではここに生成された関数を実際にpageから呼び出してみましょう。
pages/jobs/practice.vueを作りました。
画像の中の解説を見るとわかりやすいと思います。
ではこのpractice.vueをSSRにしてみます。
せっかく書いた上のコードを全部↓このように変えてください
<script setup lang="ts">
import { useJobsQuery } from '~~/generated/operations'
//
import type { JobsQuery } from '~~/generated/operations'
type JobsDataType = JobsQuery['Jobs']
//useFetchの場合、エンドポイントがURLとなる。
//今回はgraphQLなので、graphql code genが生成した関数から取得するので、
//そういう時はuseAsyncDataを使う(RESTのようにURLから撮るときはuseFetch)
const { data } = await useAsyncData('JobListData', async () => {
//useAsyncDataを使って取得したデータはキャッシュされる、そのキーが第一引数にセットされる
//第二引数で渡されたasync関数の中で、データを取得してjobsDataに入れている
const { result: jobsData, loading } = useJobsQuery()
return {
jobsData: jobsData.value as JobsDataType,
loading: loading,
}
})
</script>
<template>
<div>
<h1>求人一覧</h1>
<div v-if="data?.loading">求人読み込み中</div>
<div v-else>
<ul>
<li v-for="job in data?.jobsData?.data" :key="job.id">
<NuxtLink :to="`/sample/jobs/${job.id}`">
<div>
<h2>{{ job.job_pr }}</h2>
<p>ID: {{ job.id }}</p>
</div>
</NuxtLink>
</li>
</ul>
</div>
</div>
</template>
<style lang="sass"></style>
コードに解説を書きました。
せっかく書いたコードを全部変えることになったので猫型の布をラクダにあげてストレス解消しておきました。
こちらのリプレイスしたコードは、
vue3 nuxt3 graphqlの組み合わせなので、
SSRにするためにはuseFetchでデータを持ってくるか、
useAsyncDataでデータを持ってくるか、
いずれかをすればSSRで取得できます。
これは関数の実行タイミング故です。
RESTのようにエンドポイントがURLのようになってるならuseFetchでいいのですが、そうではない場合は、useAsyncDatasを使ってデータを持ってきましょう。
関係ないですが、vue3 nuxt3 graphqlのyoutubeでこの動画が一番楽しそうでした。
状態管理していきます。
piniaというモジュールを使って状態管理していきます。
フロントエンドのルートで、
これを実行します。
yarn add @pinia/nuxt
インストールしたらpiniaの設定をnuxt.config.jsで行っていきます。
nuxt.config.jsのmoduleの中に使うモジュールたちを配列で記述していくので、
今回はapolloやimage-edgeも使うこともありこのように書きます。
modules: ['@nuxtjs/apollo', '@nuxt/image-edge', '@piniaunuxt'],
全体のコードとしては
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
modules: ['@nuxtjs/apollo', '@nuxt/image-edge', '@piniaunuxt'],
runtimeConfig: {
public: {
cookieSessionName: process.env.COOKIE_SESSION_NAME,
},
},
typescript: {
shim: false,
strict: true,
typeCheck: true,
},
image: {
// Options
},
apollo: {
clients: {
default: {
httpEndpoint:
process.env.GRAPHQL_END_OPOINT || 'http://local.5159289.jp/graphql',
httpLinkOptions: {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
},
},
},
},
})
こうなります。
次にrootの直下にstoreというフォルダを作ります。
今回、求人関係のデータを保持したいのでjob.tsを作ります。
このjob.tsのコードはこうなっています
import { defineStore } from 'pinia'
#graphqlを実行する関数をインポートしてる
import { useJobsQuery } from '~~/generated/operations'
#JobsQueryの型を持ってきてる
import type { JobsQuery } from '~~/generated/operations'
type JobListType = JobsQuery['Jobs']
//第一引数はこの状態管理のキー名(ここではjobListStore)
export const useJobListStore = defineStore('jobListStore', {
//このdefineStoreの関数は
//state , action gettersを設定することができる(今のコードはgetterは書いていない)
state: () => ({
//jobsはjobListTypeの型ですよと定義してる書き方
//初めはundefinedが入ってて、値が入ってくるならJobListTypeの型でないといかんですよという設定です。型を設定するとVSコードを使用してる場合にこのオブジェクトを使う場合に何のキーがそのオブジェクトの中にあるのか等を表示してくれるので便利です
jobs: undefined as JobListType,
loading: true,
}),
actions: {
//上で定義したstateのjobsを書き換えるための関数
async fetchJobList() {
//まずjsの性質として{x:[x1:1,x2:2],y:[y1:1,y2:2]} という結果を返すオブジェクトのtestというものがある時に、
const {x , y} = testと書くと、
xには[x1:1,x2:2]
yには[y1:1,y2:2]が入ってくるという性質がある。
その上で、
useJobsQuery()の返り値にはloadingというキー名でboolian値が返ってくる
//第一引数の loading:XXXのloadingはロード中にはtrueが返ってくる仕様であるuseJobsQueryのboolianを受け取るために書かれている。
//useJobsQueryが実行されているときはloadingの中にtrueが返され続ける
//そして実行が終わるとfalseが返ってくる性質がある。
//ただ、それをloadingという変数ではなくて他の変数として受け取りたい時は
//loading: xxx として書くと、このxxxにtrueやfalseが返ってきてくれる
//onResultは関数の実行が終わった後に実行される
//第二引数のこのonResultはonResultという名前でないといけない。
const { loading: jobListLoading, onResult } = useJobsQuery()
this.loading = jobListLoading.value
//userJobsQueryの結果はresultに入ってくる
onResult((result) => {
this.jobs = result.data.Jobs
this.loading = jobListLoading.value
})
},
},
})
では、このfetchJobList()を使って求人一覧ページを作っていきましょうか。
コードの中にめちゃめちゃ解説書きました。
<script setup lang="ts">
import { useJobListStore } from '~~/store/job'
//importされたuseJobListStoreをインスタンス化してるようなイメージ
const jobListStore = useJobListStore()
//useAsyncDataはNuxt3の標準の関数で、SSRでレンダリング前に実行される関数
//第一引数のJobListDataは結果を受け取るキーとして指定される
await useAsyncData('JobListData', async () => {
jobListStore.fetchJobList()
return 'Done'
})
</script>
<template>
<div>
<h1>求人一覧</h1>
<div v-if="jobListStore.loading">求人読み込み中</div>
<div v-else>
<ul>
<!-- ?は、jobsがundefinedだったら無視してundefied出なかったらforを回す書き方 -->
<li v-for="job in jobListStore.jobs?.data" :key="job.id">
<div>
<h2>{{ job.job_pr }}</h2>
<p>ID: {{ job.id }}</p>
</div>
</li>
</ul>
</div>
</div>
</template>
<style lang="sass"></style>
これで、graphqlの設定をcodegen.ymlの設定を書いた上で、
yarn graphql-codegenを実行し、
generatedフォルダの中に実行用の関数を自動生成しておき、
非同期通信をするために、
storeフォルダの中のjob.tsの中で、piniaを使って、
その生成された関数を呼び値を呼び出す関数を定義しておき、
それをpagesから呼び出して使うということができました。
vue3 nuxt3 grapjhqlの技術としてはこちらのgithubが良さそうなコードなんですかね。。
でもこれまで解説したことが最低限わかっていないと「で、何からやったらいいの?」ってなってしまうと思います。
■最後にnuxt3 graphqlをテーマにして書いてるきょんさんという方のブログが分かりやすくていいなと思ったのと、
実際にちょっとnuxt3とgraphqlを書いて学びたい人向けにファブル01さんのブログがとても良かったので共有します。
最後にまたちょっと疲れたので軽く飲んで寝ます。
では。