この記事は
FORK Advent Calendar 2019 の 23日目の記事です。
Vue.js (Vue CLI with TypeScript) と Qiita API で Oraganization ビューアを作ったお話です。
モチベーション
ふと先日**「アドベントカレンダーでいちばん多くいいね!を獲得したユーザに、サンタが何かをプレゼントしてくれれば、良質な記事が社内のエンジニアたちからわしゃわしゃ集まる」**ことに気が付いてしまいました。そこで Qiita API と Vue CLI を使って、弊社の Qiita メンバーの月毎の投稿記事の「いいね!」数を表示する Qiita ビューアを作りました。
おことわり
- 突貫で開発を行ったため、アプリの挙動が若干あやしい**(言い訳)**
- 一般公開すると Qiita API のリクエスト制限数(後述)を超える可能性がある**(多分ないけど)**
以上の理由から完成版の URL は開示しておりません。
開発のポイントと併せて、サンプルのコードを記載しておりますので、なんとなく実装の内容をお伝えできれば幸いです。
完成したアプリのキャプチャ
こんな感じのアプリです。本日までのランキングでは @yoh_zzzz さんの Nuxt + Firebase の記事がトップですね。
開発のポイント
開発の要所を 3 分間クッキング でお届けします。
1. Qiita API のインターフェースを定義する
開発は TypeScript で行います。(しれっと言い切るスタイル)
ブラウザで API を叩いたときのレスポンスや、JSON Schema を確認しながら、API のインターフェースを定義します。型定義ファイルはどこに配置しても良いのですが、僕は Vuex のモジュールディレクトリに用意しました。余談ですが、個人的にこの作業がとても楽しく、不安になります。
▼ ユーザ周りインターフェース
export interface QiitaUser {
description: string | null
facebook_id: string | null
followees_count: number
followers_count: number
github_login_name: string | null
id: string
items_count: number
linkedin_id: string | null
location: string | null
name: string | null
organization: string | null
permanent_id: number
profile_image_url: string
team_only: boolean
twitter_screen_name: string | null
website_url: string | null
}
export interface UserState {
list: QiitaUser[]
}
開くとソースコードが表示されます。
投稿記事周りインターフェース
export interface QiitaGroup {
created_at: string
id: number
name: string
private: boolean
updated_at: string
url_name: string
}
export interface QiitaTag {
name: string
versions: string[]
}
export interface QiitaPost {
rendered_body: string
body: string
coediting: boolean
comments_count: number
created_at: string
group: QiitaGroup | null
id: string
likes_count: number
private: boolean
reactions_count: number
tags: QiitaTag[]
title: string
updated_at: string
url: string
user: QiitaUser
page_views_count: number | null
}
export interface PostState {
list: QiitaPost[]
}
2. API 呼び出し時にリクエストトークンを含める
Qiita API は get リクエストの場合はトークンなしでも呼び出しが可能なのですが、トークンの有り/無しで 利用制限 (1 時間あたりに呼び出しが可能な回数)に大きな差があります。60 回 / 1 時間以上のリクエストが必要な場合はトークンが必要にります。トークンの取得は OAuthを利用した認可フローか、ユーザの管理画面 から発行できます。今回は時間もなかったため後者を選択しました。
以下は、Vuex store モジュールの action として定義した、投稿記事取得 API のリクエストです。
投稿記事取得 action
const actions: ActionTree<PostState, RootState> = {
async fetchPosts(context: ActionContext<PostState, RootState>, payload: { userId: string, page?: number, perPage?: number }): Promise<QiitaPost[]> {
return new Promise(async (resolve, reject) => {
const url: string = `https://qiita.com/api/v2/users/${payload.userId}/items?page=${payload.page}&per_page=${payload.perPage}`
const accessToken: string = 'XXXXXXXXX'
const config: AxiosRequestConfig = {
params: { time: Math.floor(Date.now() / 1000 / 60 / 60) }, // キャッシュ対策(1時間毎に異なる文字列を設定)
headers: { Authorization: `Bearer ${accessToken}` }, // リクエストヘッダにトークンを含める
}
const response: AxiosResponse<QiitaPost[]> = await axios.get<QiitaPost[]>(url, config)
if (response.status === 200) {
const posts: QiitaPost[] = response.data
if (posts.length) context.commit('add', posts)
resolve(posts)
} else {
reject(response.status)
}
})
},
}
export default actions
3. リクエスト回数を最小限にする
一意の Ogranization に所属している Qiita ユーザを取得する API が用意されていないため、弊社の Organization 所属ユーザは、ハードコーディングで用意した静的 JSON ファイルにユーザ ID を列挙することにしました。ユーザ情報は、/api/v2/users/:user_id から取得できるのですが、この API から各ユーザの投稿記事の合計いいね!数は取得することはできず、すべての投稿記事から各々のいいね!数を合算する必要があります。また投稿記事一覧を取得する API (/api/v2/users/:user_id/items) の戻り値に、記事のユーザ情報がそのまま含まれているため、全所属ユーザの記事一覧を取得し、そこからユーザリストを抽出する Vuex の getters 関数を用意しました。
ユーザリスト取得 getters
export const getters: GetterTree<PostState, RootState> = {
getUsers(state: PostState): QiitaUser[] {
const users: QiitaUser[] = []
state.list.forEach((post: QiitaPost) => {
const user: QiitaUser = post.user
if (!users.some((usr: QiitaUser): boolean => usr.id === user.id)) {
users.push(user)
}
})
return users
},
}
export default getters
なお記事取得 API では、1 回のリクエストで最大 100 件の投稿記事を取得することができます。投稿記事数が 100 件を超えるユーザについては、未取得の残りの記事を追加でリクエストするようにしました。
一連の流れを整理すると以下になります。
- Organization 所属のユーザ ID を自分で用意した JSON から読み込み
- 全ユーザの投稿記事を 100 件ずつ API から取得 (Vuex action)
- 取得した投稿記事からユーザー情報を取得 (Vuex getters)
- 投稿記事が 100 件を超えるユーザについて、残りの記事を取得 (Vuex action)
おわりに
駆け足での解説となりましたが、ざっくりと要所の雰囲気だけでも伝わったでしょうか。
おそらく今年最後の投稿になるであろう本記事にて Vue のことを書きましたが、実際の仕事でも 2019 年は Vue が活躍してくれました。来年は Vue 3.0 と React を学ぼうと思ってます。
それではみなさま良い年末年始をお過ごしください。
FORK Advent Calendar 2019
22日目 正規表現にマッチした文字列を replace を使わずにハイライトさせる @yshrkn
24日目 Nuxt.jsとmysqlを連携してデータを表示してみた @ktn