Nuxt.js+TypescriptでQiitaAPIを叩いて画面に表示する

とある勉強会のネタで使うのでHowToがてら記事にしておこうかとおもいます。

## やってみること

- Node.jsの環境構築

- qiitaAPIを使う準備

- Nuxt.jsの導入

- Nuxt.jsのTypescript設定

- Nuxt.jsからqiitaAPI叩いて画面作ってみる


## Node.jsの環境構築
私はフルスタックなんですが、いろんな言語を使うので、XXenvをまとめて管理をできる[anyenv](https://github.com/anyenv/anyenv)なる便利なものを使ってます。
せっかくなので、それを入れてみます。

```shell
# anyenvのsetup
$ git clone https://github.com/anyenv/anyenv ~/.anyenv
$ echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.zshrc
$ ~/.anyenv/bin/anyenv init
$ anyenv install --init
$ anyenv install nodenv
$ exec $SHELL -l

# 各envのsetup
$ nodenv install -l
$ nodenv install x.x.x # なるべく最新のを入れる
$ nodenv global x.x.x

# 記事の内容には使わないけど私のメモがてら他のenvも
$ anyenv install goenv
$ anyenv install tfenv
$ anyenv install rbenv
$ anyenv install pyenv
$ exec $SHELL -l

$ goenv install -l
$ goenv install x.x.x
$ goenv global x.x.x

$ tfenv list-remote
$ tfenv install x.x.x
$ tfenv global x.x.x

$ rbenv install -l
$ rbenv install x.x.x
$ rbenv global x.x.x

$ pyenv install -l
$ pyenv install x.x.x
$ pyenv global x.x.x

# 全部のenvのversionが表示されるので便利
$ anyenv versions
```

## qiitaAPIを使う準備
- qiitaにログインする

- アクセスTokenを[ここ](https://qiita.com/settings/applications)で発行する

- APIリファレンスは[こちら](https://qiita.com/api/v2/docs)

## Nuxt.jsの導入

```shell
# yarnをメインに使うので入れておく
$ brew install yarn 

# nuxtのインストール
$ npx create-nuxt-app nuxt-qiita-example

? Project name # そのままenter
? Project description #そのままenter
? Author name # そのままenter
? Choose the package manager
❯ Yarn # を選択してenter
? Choose UI framework
❯ None # 今回は使わないのでこれを選択してEnter
? Choose custom server framework
❯ Express # を選択してenter(BffAPI作るときに必要なので馴染みのあるのを入れておく)
? Choose Nuxt.js modules
❯◉ Axios # をSpaceキーで選択してenter
? Choose linting tools
 ◉ ESLint
❯◉ Prettier # 定番ツール使いたいのでこの２つをSpaceキーで選択してenter
? Choose test framework
❯ None # 今回は使わないのでこれを選択してEnter
? Choose rendering mode
❯ Universal (SSR) # を選択してenter
```
[公式のInstall](https://ja.nuxtjs.org/guide/installation)

## Nuxt.jsのTypescript設定
```shell
$ yarn add -D @nuxt/typescript
$ yarn add ts-node
$ yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
$ touch tsconfig.json
$ yarn run build # nuxtの起動(初回起動時にtsconfig.jsonに初期値設定値が入る)
```
`nuxt.config.js`を`nuxt.config.ts`にリネームして以下になるように書き換える

```ts
import NuxtConfiguration from '@nuxt/config'

const config: NuxtConfiguration = {
  mode: 'universal',
  /*
   ** Headers of the page
   */
  head: {
    title: process.env.npm_package_name || '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: process.env.npm_package_description || ''
      }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },
  /*
   ** Customize the progress-bar color
   */
  loading: { color: '#fff' },
  /*
   ** Global CSS
   */
  css: [],
  /*
   ** Plugins to load before mounting the App
   */
  plugins: [],
  /*
   ** Nuxt.js dev-modules
   */
  devModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module'
  ],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios'
  ],
  /*
   ** Axios module configuration
   ** See https://axios.nuxtjs.org/options
   */
  axios: {},
  /*
   ** Build configuration
   */
  build: {
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) {}
  }
}

export default config
```

.eslintrc.jsをtypescriptに対応させる

```js
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true
  },
  plugins: ['prettier', '@typescript-eslint'],
  parserOptions: {
    parser: '@typescript-eslint/parser'
  },
  extends: [
    '@nuxtjs',
    'plugin:nuxt/recommended',
    'plugin:prettier/recommended',
    'prettier',
    'prettier/vue',
    'prettier/@typescript-eslint'
  ],
  rules: {
    'nuxt/no-cjs-in-config': 'off',
    'no-unused-vars': 'off',
    '@typescript-eslint/no-unused-vars': 'error'
  }
}
```

[公式のTSsupport](https://ja.nuxtjs.org/guide/typescript)

ここまでは、公式サポートに書いてあるものの抜粋ですが、Typescriptを使う場合でも通常のJavascriptを使う場合でも`create-nuxt-app`で初回インストールした人は、ここからがハマりどころ。

### ハマりどころ その１. Packageが古い！
まず、公式のcreate-nuxt-appでinstallしたnuxt.jsですが、package.jsonをみると

```js
  "dependencies": {
    "@nuxtjs/axios": "^5.3.6",
    "cross-env": "^5.2.0",
    "express": "^4.16.4",
    "nuxt": "^2.0.0",
    "ts-node": "^8.3.0"
  },
```
あれ、nuxtのversion古い。。。となります。
create-nuxt-appのテンプレートをアップデートしてほしいところではありますが、自分で最新版にあげてきます。

packageのversionを更新するのに
yarn に`yarn upgradeInteractive`はありますが、package.jsonは書き換えてくれないので、手で書き換えるのは面倒なので`ncu`を使えるようにしてversionを上げます。

```shell
$ npm install -g npm-check-updates
# ターミナルを立ち上げ直して
$ ncu # 依存パッケージの最新versionを確認
$ ncu -u # 依存パッケージを最新にアップデート
$ yarn install # 最新の依存packageをインストール
```

### ハマりどころ その2. scriptsの定義が古い
生成されたものはこれですが、nuxt-templateのソースコードを読んだところ、選択したフレームワークによって生成されるpackage.jsonの書き方が違うようで、選択したものによっては、nuxtコマンドの使い方が非常に古いです。そうなると初見だとハマりどころ。。。
公式documentの`スクラッチから始める`などをみると正しいコマンド書いてありますが、全部のっていないのとあちこちのdocumentに点在してるので正しくするとこうなります。ついでに対象をtsにしておきます。

#### 修正前
```js
  "scripts": {
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
    "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
    "build": "nuxt build",
    "start": "cross-env NODE_ENV=production node server/index.js",
    "generate": "nuxt generate"
  },
```

#### 修正後
```js
  "scripts": {
    "lint": "eslint --ext .ts,.vue --ignore-path .gitignore .",
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate"
  },
```
### ハマりどころ その3. server/index.jsはいらない
その２の修正で、nodemonがなくなってると思いますが最新だとnuxtコマンドがnode-serverを立ててくれるため基本的にはserver/index.jsは不要なので一度消しちゃいます。
server/index.jsを使う場合は、nuxtの
`serverMiddlewares`
を利用するときですね。

以上で、Nuxt.js+Typescriptの初回環境構築は完了です。

## Nuxt.jsからqiitaAPI叩いて画面作ってみる

### 準備
Typescriptで開発始める前に[公式](https://ja.nuxtjs.org/guide/typescript#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88)で推奨している`vue-property-decorator`を追加したいと思うのですが、これだとnuxtが用意している`asyncData`が使えません。あとでAPI呼び出しでasyncDataを使うため、nuxt-communityが出してる[nuxt-property-decorator](https://github.com/nuxt-community/nuxt-property-decorator)を入れます。

```sh
$ yarn add nuxt-property-decorator
```

### 最低限必要なもの
Nuxt.jsはコードにルールを持たせるため、ディレクトリ構造も決まっています。
今回画面を作るうえで最低限必要な`pages`ディレクトリのみ使います。

他ディレクトリの説明は公式の[ディレクトリ構造](https://ja.nuxtjs.org/guide/directory-structure)を参照してみてください。

`pages`は簡単にいうと
`https://qiita.com/list`
というURLがあったとして、`https://qiita.com/`の
`/`以降のURLの画面を表しています。

上のURLの場合、

```
pages
 ┠ list
 ┃  ┗ index.vue  # https://qiita.com/list のページです。
 ┗ index.vue # https://qiita.com/ のdomainの直下のページです。
```
というディレクトリ構造になり、ディレクトリ名がそのままURLになります。
index.vueというファイル場合にすると、htmlでよくあるindex.htmlと同じようにURL省略できます。
(`https://qiita.com/list/index` としなくていい。`https://qiita.com/list`と省略可)

他にも表現方法はありますが、ここでは割愛します。詳しくみたい方は公式の
[ルーティングの基礎](https://ja.nuxtjs.org/guide/routing)
を参照してください。

それでは実際にページを作っていきます。
まず`pages`の下に`list`ディレクトリを作り、その下に、index.vueファイルをつくります。

### まずはhello world
作ったindex.vueのファイルに以下を記述し画面を表示してみます。

```vue
<template>
  <h1 class="red">Hello {{ name }}!</h1>
</template>

<style>
.red {
  color: red;
}
</style>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'

@Component({})
export default class List extends Vue {
  data() {
    return {
      name: 'World'
    }
  }
}
</script>
```

Nuxtのサーバーを起動します。

```sh
$ yarn run dev
```

ブラウザで`http://localhost:3000/list`アクセスしてみると、赤文字のHello Worldが表示できたかと思います。

vueファイルについてちょっと解説します。

### vueファイルの作りについて
主にこの３つのディレクティブ（タグみたいなところ）からできています。

```
・template: HTMLを書くところ
・style: CSS書くところ
・script: 画面で使う変数の定義や、ボタンクリック時の処理、API呼び出してデータ取得するなど、いろいろな処理を書きます
```

vueファイルは画面が主なので、`template`は必須ですが、CSSの記載が不要なら`style`は不要ですし、変数やイベントを持たない静的なページについては、`script`も不要です。

### dataメソッドについて

dataメソッドの戻り値がそのままtemplateで呼び出せます。サンプルだと
returnで `name` を返してると思いますが、templateでは`{{ name }}`で変数の値を表示することができます。dataメソッドは同期呼び出しのみなので、apiから返却された値を使いたい場合は、非同期呼び出しになるので`asyncData`を使います。

### APIの呼び出し

APIの呼び出しは[asyncData](https://ja.nuxtjs.org/api/)で`axios`を呼び出して利用します。[公式](https://ja.nuxtjs.org/guide/typescript)だとimportでaxiosを呼び出してますが、すでにnuxtのcontextにaxiosがいるのでそれを利用します。

[記事の一覧](https://qiita.com/api/v2/docs#get-apiv2items)を呼び出し、returnでAPIのレスポンスを返します。

```ts
  async asyncData(context) {
    const res = await context.$axios.get(
      'https://qiita.com/api/v2/items',
      {
        headers: {
          Authorization: 'Bearer 準備で発行した自分のトークン'
        }
      }
    )
    return {
      data: res.data
    }
  }
```

記事の一覧APIはArrayで返ってくるので、templateで一覧表示をしてみます。
templateはvue.jsの構文をそのまま使えるので
`v-for`を使って記事一覧のタイトルを出してみます。ついでにstyleも少し整えます。

```vue
<template>
  <ul class="list">
    <li v-for="item in data" :key="item.id" class="listItem">
      {{ item.title }}
    </li>
  </ul>
</template>

<style>
.list {
  padding: 0;
}

.listItem {
  list-style: none;
  border-bottom: 1px solid #999;
  padding: 1rem 16px;
  box-sizing: border-box;
}
</style>
```

APIから取ってきた記事一覧のタイトルが画面に表示されるかと思います。
これで基本的なAPIの呼び出しから画面の表示までの流れができました。

いかがでしたでしょうか？

今回のサンプルは[github](https://github.com/tanaka-yui/nuxt-qiita-example)に上げておきます。

次回の記事を書くとしたら、`serverMiddlewares`の機能利用して、APIを作って、tokenを隠してみたり、ページングなどを実装してみたりとか書けたらと思います。