한번에 Nuxt.제이스와 아폴로로 GraphiQL 분위기를 잡은 하슨.

본문의 목적


Nuxt.제이스와 아폴로 클라이언트의 손잡이를 사용해 그래픽QL의 분위기를 잡기 위해서다.
백엔드는 Pokemon API를 사용합니다.
드디어 다음 포켓몬 도감을 완성했습니다.
(Deploy 작업과 관련되지 않음)
https://nuxt-graphql-demo.netlify.app/

개발 환경

  • macOS
  • node v14.15.5
  • GraphiQL 소개

  • API용 쿼리 언어
  • 특징은 다음과 같은 몇 가지가 있다.
  • REST에는 여러 개의 endipoint가 존재하는데 용도에 따라 리퀘스트를 보내고 불필요한 데이터를 포함하는response가 존재한다. 한편, GraphiQL에서는 단일endpoint에 대해 원하는 정보를 지정하여 리퀘스트,response를 얻을 수 있다.
  • 리퀘스트를 지정할 때 필요한response를 통해 전방 개발이 쉬워졌다
  • 개발을 시작하다


    프로젝트 작성

    npx create nuxt-app 명령을 사용하여 프로젝트를 만듭니다.
    
    npx create-nuxt-app nuxt-graphql-example
    
    
    대화형 Nuxt.js 프로그램을 시작할 수 있습니다.
    이번 선택은 다음과 같습니다.
    Programming language는 타입(이하 TS)을 선택했지만, 이 앱에서는 금형을 사용하는 이점이 거의 없어 자바스크립트를 활용해 개발할 예정이다.
    또한 UI 구축을 단순화하기 위해 Vuetify를 사용합니다.결과적으로 노동시간이 많이 줄었다.
    
    ? Programming language: TypeScript
    ? Package manager: Yarn
    ? UI framework: Vuetify.js
    ? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
    ? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
    ? Testing framework: Jest
    ? Rendering mode: Universal (SSR / SSG)
    ? Deployment target: Server (Node.js hosting)
    ? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
    ? Continuous integration: None
    ? Version control system: Git
    
    
    현재yarn dev 로컬 서버의 부팅을 확인할 수 있습니다.
    Screenshot 2021-05-03 at 14.02.50.png

    Apollo Client 설정


    Apollo Client를 사용하여 GraphiQL이 있는 Request를 보냅니다.
    라이브러리 설치
    
    yarn add @nuxtjs/apollo graphql-tag
    
    
    nuxt.config.js에서client를 읽는 설정을 합니다.
    
    // nuxt.config.js
    
    // Modules (https://go.nuxtjs.dev/config-modules)
      modules: [
        '@nuxtjs/apollo',
      ],
    
      // Apollo module configuration
      apollo: {
        clientConfigs: {
          default: {
            httpEndpoint: 'https://graphql-pokemon2.vercel.app/',
          }
        }
      },
    
    TSgql.d.ts에 대응하기 위해 새로 만들고 기술합니다.
    
    // 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
    }
    
    

    실행할 질의 준비


    프로젝트 아래에 새 디렉터리apollo/queries를 만들고 조회를 준비합니다.
    
    ├── apollo
    │   └── queries
    │       ├── pokemon.gql
    │       └── pokemons.gql
    
    
  • pokemon.gql
  • id와 파라미터에 대응하는pokemon의 정보 가져오기
  • $id:String!String형에서 인자(Non-nullllable)
  • 를 받아들여야 합니다
    
    # pokemon.gql
    
    query pokemon($id: String!) {
      pokemon(id: $id) {
        name
        classification
        types
        resistant
        weaknesses
        evolutions {
          name
          id
          image
        }
        evolutionRequirements {
          name
          amount
        }
        image
      }
    }
    
    
    
  • pokemons.gql
  • $amount:Int!에서 얻은 포켓몬의 종류수는 반드시 Int형으로 수신해야 한다(Non-nulllable)
  • 
    # pokemons.gql 
    
    query pokemons($amount: Int!) {
      pokemons(first: $amount) {
        id
        name
        image
      }
    }
    
    
    

    UI 구축(pages)

    pages 페이지 하이픈에 사용할 파일을 준비합니다.
    
    ├── pages
    │   ├── index.vue
    │   ├── pokemon
    │   │   └── _id.vue
    │   └── pokemons.vue
    
    
    
  • index.vue
  • 첫 페이지
  • pokemons.vue
  • 포켓몬스터 일람페이지
  • pokemon/_id.vue
  • 포켓몬스터 상세 페이지
  • index.vue


    사이트 설명 페이지.
    취향에 맞게 맞춤 제작하세요.
    
    <template>
      <v-container fill-height>
        <v-row justify="center" align-content="center" class="">
          <v-col cols="12" align-self="auto">
              <h1 class="text-center">This is the demosite by using Nuxt.js + GraphQL(Apollo)</h1>
          </v-col>
          <v-col cols="12" align-self="auto"> 
              <h2 class="text-center">You can play the pokemomn picture book from sidebar menu</h2>
          </v-col>
        </v-row>
      </v-container>
    </template>
    
    
    
    
    Screenshot 2021-05-03 at 15.27.11.png

    pokemons.gql


    포켓몬스터<script>에서 apolloobject를 사용하면graphiQL의 Request를 진행할 수 있습니다.
    respponseapollo는 Object에서 발표한 pokemons를 값으로 사용할 수 있습니다.
    
    <template>
      <v-container fluid>
        <v-row dense>
          <v-col v-for="pokemon in pokemons" :key="pokemon.id" :cols="12">
            <v-card>
              <NuxtLink :to="`pokemon/${pokemon.id}`">
                <v-img
                  :src="pokemon.image"
                  class="white--text align-end"
                  gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)"
                  contain=true
                  height="800"
                >
                  <v-card-title v-text="pokemon.name"></v-card-title>
                </v-img>
              </NuxtLink>
    
              <!-- <v-card-actions>
                <v-spacer></v-spacer>
                <v-btn icon>
                  <v-icon>mdi-heart</v-icon>
                </v-btn>
    
                <v-btn icon>
                  <v-icon>mdi-bookmark</v-icon>
                </v-btn>
    
                <v-btn icon>
                  <v-icon>mdi-share-variant</v-icon>
                </v-btn>
              </v-card-actions> -->
            </v-card>
          </v-col>
        </v-row>
        <br>
        <v-expansion-panels accordion>
          <v-expansion-panel>
            <v-expansion-panel-header>Show Query Result</v-expansion-panel-header>
            <v-expansion-panel-content>
              {{ pokemons }}
            </v-expansion-panel-content>
          </v-expansion-panel>
        </v-expansion-panels>
      </v-container>
    </template>
    <script>
    import "vue-apollo";
    import pokemons from "~/apollo/queries/pokemons.gql";
    
    // pokemonは最大151匹
    let numGetPokemons = 151;
    
    export default {
      data() {
        return {
          pokemons
        };
      },
      // pokemon一覧を取得
      apollo: {
        pokemons: {
          prefetch: "loading",
          query: pokemons,
          variables: {
            amount: numGetPokemons
          }
        }
      },
    };
    </script>
    
    
    Screenshot 2021-05-03 at 15.34.27.png

    pokemon/_id.vue


    포켓몬 일람페이지NuxtLink를 사용하여 포켓몬 상세 페이지를 동적으로 생성합니다.
    렌더링 시 리소스 pokemon 를 가져올 수 없어서 오류가 발생했습니다. v-if="pokemon" 완성되면 페이지가 표시됩니다.prefetch: ({ route }) => ({ id: route.params.id })variables() {return { id: this.$route.params.id };}를 사용하여 path내의 포켓몬 id를 획득하여 pokemon 조회의 변수로 사용합니다.
    ※ 사용하는 구성 요소singleExplanation, multiExplanation, evolutionExplanation는 다음 장에서 설명합니다.
    
    <template>
      <div v-if="pokemon">
        <v-container fluid>
          <v-img
            :src="pokemon.image"
            class="white--text align-end"
            gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)"
            contain=true
            height="800"
          >
            <v-card-title v-text="pokemon.name"></v-card-title>
          </v-img>
          <v-expansion-panels focusable>
            <!-- classification -->
            <single-explanation
              referKey="Classification"
              :referValue="pokemon.classification"
            />
            <!-- types -->
            <multi-explanation referKey="Types" :referValue="pokemon.types" />
            <!-- resistant -->
            <multi-explanation
              referKey="Resistant"
              :referValue="pokemon.resistant"
            />
            <!-- weaknesses -->
            <multi-explanation
              referKey="Weaknesses"
              :referValue="pokemon.weaknesses"
            />
            <!-- evolutions -->
            <evolution-explanation
              v-if="pokemon.evolutions"
              referKey="Evolutions"
              :referValue="pokemon.evolutions"
            />
            <!-- evolutionRequirements -->
            <multi-explanation
              v-if="pokemon.evolutionRequirements"
              referKey="EvolutionRequirements"
              :referValue="pokemon.evolutionRequirements"
            />
    
            <!-- レスポンス -->
            <v-expansion-panel>
              <v-expansion-panel-header>Show Query Result</v-expansion-panel-header>
              <v-expansion-panel-content class="justify-center">
                {{ pokemon }}
              </v-expansion-panel-content>
            </v-expansion-panel>
          </v-expansion-panels>
        </v-container>
      </div>
    </template>
    
    <script>
    import pokemon from "~/apollo/queries/pokemon.gql";
    import multiExplanation from "~/components/multiExplanation.vue";
    import singleExplanation from "~/components/singleExplanation.vue";
    import evolutionExplanation from "~/components/evolutionExplanation.vue";
    
    export default {
      apollo: {
        pokemon: {
          query: pokemon,
          prefetch: ({ route }) => ({ id: route.params.id }),
          variables() {
            return { id: this.$route.params.id };
          }
        }
      },
      components: {
        multiExplanation,
        singleExplanation,
        evolutionExplanation
      }
    };
    </script>
    
    

    UI 구성(components)


    UI를 표시할 components를 만듭니다.singleExplanation, multiExplanation, evolutionExplanation 구성 요소를 생성합니다.
    
    ├── components
    │   ├── evolutionExplanation.vue
    │   ├── multiExplanation.vue
    │   └── singleExplanation.vue
    
    

    singleExplanation


    
    <template>
        <v-expansion-panel>
          <v-expansion-panel-header>{{ referKey }}</v-expansion-panel-header>
          <v-expansion-panel-content class="text-center">
            {{ referValue }}
          </v-expansion-panel-content>
        </v-expansion-panel>
    </template>
    <script>
    export default {
      props: ["referKey", "referValue"]
    };
    </script>
    
    
    

    multiExplanation


    
    <template>
      <v-expansion-panel>
        <v-expansion-panel-header>{{ referKey }}</v-expansion-panel-header>
        <v-expansion-panel-content v-for="(value, key) in referValue" :key="key" class="text-center">
          {{ value }}
        </v-expansion-panel-content>
      </v-expansion-panel>
    </template>
    <script>
    export default {
      props: ["referKey", "referValue"]
    };
    </script>
    
    

    evolutionExplanation

    NuxtLink로 진화한 포켓몬 링크를 붙입니다.
    
    <template>
      <v-expansion-panel>
        <v-expansion-panel-header>{{referKey}}</v-expansion-panel-header>
        <v-expansion-panel-content v-for="(value, key) in referValue" :key="key" class="text-center">
          <NuxtLink :to="`${value.id}`">
          {{value.name}}
          </NuxtLink>
        </v-expansion-panel-content>
      </v-expansion-panel>
    </template>
    <script>
    export default {
      props:[
        'referKey',
        'referValue'
        ]
    };
    </script>
    
    
    Screenshot 2021-05-03 at 15.53.13.png

    UI 빌드(기타)


    취향 변경default.vue에 따라 Sidebar의 항목과layout을 조정합니다
    
    ├── layouts
    │   ├── default.vue
    │   └── error.vue
    
    
    
    <template>
      <v-app dark>
        <v-navigation-drawer
          v-model="drawer"
          :mini-variant="miniVariant"
          :clipped="clipped"
          fixed
          app
        >
          <v-list>
            <v-list-item
              v-for="(item, i) in items"
              :key="i"
              :to="item.to"
              :href="item.href"
              router
              exact
            >
              <v-list-item-action>
                <v-icon>{{ item.icon }}</v-icon>
              </v-list-item-action>
              <v-list-item-content>
                <v-list-item-title v-text="item.title" />
              </v-list-item-content>
            </v-list-item>
          </v-list>
        </v-navigation-drawer>
        <v-app-bar
          :clipped-left="clipped"
          fixed
          app
        >
          <v-app-bar-nav-icon @click.stop="drawer = !drawer" />
          <v-toolbar-title v-text="title" />
          <v-spacer />
        </v-app-bar>
        <v-main>
          <v-container>
            <nuxt />
          </v-container>
        </v-main>
        <v-footer
          :absolute="!fixed"
          app
        >
          <span>&copy; {{ new Date().getFullYear() }}</span>
        </v-footer>
      </v-app>
    </template>
    
    <script>
    export default {
      data () {
        return {
          clipped: false,
          drawer: false,
          fixed: false,
          items: [
            {
              icon: 'mdi-apps',
              title: 'Welcome',
              to: '/'
            },
            {
              icon: 'mdi-format-list-bulleted',
              title: 'Pokemons',
              to: '/pokemons'
            },
            {
              icon: 'mdi-code-tags',
              title: ' Github',
              href: 'https://github.com/kimkiyong0612/Nuxt-GraphQL-Demo'
            }
          ],
          miniVariant: false,
          right: true,
          rightDrawer: false,
          title: 'Nuxt.js GraphQL(Pockemon API) Demo'
        }
      }
    }
    </script>
    
    
    

    매듭을 짓다


    지금까지 포켓몬 도감 제작이었습니다.
    그리고 herokuNetlify를 디자인해 0엔에 사이트를 공개할 수 있다.
    (Vercel에서 SSR의 Deploy가 순조롭게 진행되지 않았습니다. 성공한 사람이 있다면 저에게 알려주세요.)
    수고하셨습니다.

    Reference


    https://github.com/lucasbento/graphql-pokemon
    https://github.com/nuxt/nuxt.js/tree/dev/examples/vue-apollo
    https://vuetifyjs.com/ja/

    좋은 웹페이지 즐겨찾기