こんにちは!株式会社フォトラクションでプロダクトマネージャーをしています南風原(はえばる)です。
Photoruction Advent Calendar 2022の12日目の記事になります。
はじめに
ついに、待望のNuxt3が正式リリースされましたね👏
これからNuxt3を使ってみようと思っている初心者向けに、Nuxt3 × Vuetify × Instagram APIを用いた簡単な写真検索アプリをハンズオン形式で紹介していきたいと思います。
[前提]開発環境の各種バージョンは以下になります
os: macOS Monterey node: v18.12.1 yarn: 1.22.11
Nuxt3をインストールしよう
npx nuxi init nuxt-instagram cd nuxt-instagram yarn install yarn dev -o
Vuetify3をインストールしよう
yarn add vuetify@next mdi yarn add -D sass
Nuxt3でVuetifyを利用できるようにしよう
// ./plugins/vueitfy.ts import { createVuetify } from 'vuetify'; import * as components from 'vuetify/components'; import * as directives from 'vuetify/directives'; export default defineNuxtPlugin((nuxtApp) => { const vuetify = createVuetify({ components, directives, }); nuxtApp.vueApp.use(vuetify); });
// ./nuxt.config.ts export default defineNuxtConfig({ css: ['vuetify/lib/styles/main.sass', 'mdi/css/materialdesignicons.min.css'], build: { transpile: ['vuetify'], }, })
Instagram Graph APIを利用できる準備をしよう
Instagramプロアカウントを作成
- Instagramアカウントを作成する
- 設定から「プロアカウントに切り替える」をクリックしプロアカウントに切り替える
Facebookページを作成
Facebookアプリを作成
- Facebook for Developersにアクセスし「利用を開始」するよりFacebook for Developersアカウントを作成する
- 「アプリを作成」> 「ビジネス」> 基本情報を入力する
- FacebookアプリにInstagram APIを追加する
アクセストークンを取得
1番目のアクセストークンを取得する
- ヘッダーの「ツール」から「InstagramグラフAPIエクスプローラー」をクリック
- 作成したアプリを選択し、「ページアクセストークンを取得」をクリックし、作成したFacebookページを選択し「完了」をクリック
↓1番目のアクセストークンをコピーする
2番目のアクセストークンを取得する
// ブラウザで下記URLにアクセスする https://graph.facebook.com/v15.0/oauth/access_token?grant_type=fb_exchange_token&client_id=${アプリID}&client_secret=${app secret}&fb_exchange_token=${1番目のトークン}
{ "access_token": "2番目のトークン", "token_type": "bearer", "expires_in": 5183464 }
- 3番目のアクセストークンを取得する
// ブラウザで下記URLにアクセスする https://graph.facebook.com/v15.0/me?access_token=${2番目のトークン}
{ "name": "山田 太郎", "id": "12345678" }
上で取得したIDを使って、ブラウザで下記URLにアクセスします。
https://graph.facebook.com/v15.0/${id}/accounts?access_token=${2番目のトークン}
// 3番目のアクセストークンが発行される { "data": [ { "access_token": "3番目のトークン", "category": "xxxx", "category_list": [ { "id": "123456", "name": "xxxx" } ], "name": "Facebookページ名", "id": "FacebookページID" } ], "paging": { "cursors": { "before": "xxxx", "after": "xxxx" } } }
InstagramビジネスアカウントIDの取得
// 上で取得したIDを使って、ブラウザで下記URLにアクセスする https://graph.facebook.com/v15.0/${FacebookページID}?fields=id,name,instagram_business_account&access_token=${3番目のトークン}
// InstagramビジネスアカウントID取得結果 { "id": "FacebookページID", "name": "Facebookページ名", "instagram_business_account": { "id": "InstagramビジネスアカウントID" } }
「3番目のアクセストークン」と「InstagramビジネスアカウントID」の取得ができたので、晴れてInstagram GraphAPIの利用が可能となります!
.envに環境変数を追加しよう
.envに環境変数を登録後、nuxtから呼び出せるようにnuxt.config.tsにも定義します。
// ./nuxt.config.ts export default defineNuxtConfig({ runtimeConfig: { public: { instagramApiUrl: process.env.INSTAGRAM_API_URL, userId: process.env.USER_ID, accessToken: process.env.ACCESS_TOKEN, } } })
検索フォームを作成してみよう
// ./app.vue <template> <SearchInput /> </template>
// ./components/SearchInput.vue <script setup lang="ts"> interface State { loaded: boolean loading: boolean value: string } const state = reactive<State>({ loaded: false, loading: false, value: '' }) interface Emits { (e: 'search', value: string): void } const emit = defineEmits<Emits>() const handleSearch = () => { state.loading = true setTimeout(() => { state.loading = false state.loaded = true }, 2000) emit('search', state.value) } </script> <template> <v-text-field v-model="state.value" :loading="state.loading" density="compact" variant="solo" label="検索" append-inner-icon="mdi-magnify" single-line hide-details @click:append-inner="handleSearch" /> </template>
↓こういう感じで、表示されればOK👌
ハッシュタグ検索機能をつくろう
APIエンドポイントを定義
外部APIを直接呼び出そうとするとCORSエラーになってしまうので、server/api
にAPIエンドポイントを定義して呼び出すようにします。
ハッシュタグのIDを取得
// server/api/hashtagId.ts export default defineEventHandler ( async (event) => { const config = useRuntimeConfig() const query = getQuery(event) const result = await $fetch(`${config.public.instagramApiUrl}/ig_hashtag_search`, { params: query }) return result })
ハッシュタグの付いたメディアを取得
// server/api/recentMedia/[hashtagId].ts export default defineEventHandler ( async (event) => { const config = useRuntimeConfig() const query = getQuery(event) const result = await $fetch(`${config.public.instagramApiUrl}/${event.context.params.hashtagId}/recent_media`, { params: query }) return result })
データ取得に関するロジックを書く
composablesディレクトリにデータ取得などのロジックを書くことで、Vue側で自動インポートされて利用できるようになります。
// ./composables/useInstagram.ts import { Media } from '@/@types/instagram' export const useInstagram = () => { const config = useRuntimeConfig() const medias = ref<Media[]>([]); const getHashtagId = async (hashtag: string) => { const response = await $fetch('/api/hashtagId', { params: { user_id: config.public.userId, access_token: config.public.accessToken, q: hashtag } }) return response.data[0].id } const getRecentMedia = async (hashtag: string) => { const hashtagId = await getHashtagId(hashtag) const response = await $fetch(`/api/recentMedia/${hashtagId}`, { params: { user_id: config.public.userId, access_token: config.public.accessToken, fields: 'id,media_url,permalink,media_type,timestamp' } }) medias.value = response.data.filter(i => { if(i.media_url && i.media_type === 'IMAGE') { return true } return false }) return medias.value } return { getHashtagId, getRecentMedia, medias } }
Vue側でComposablesな関数を呼び出してみよう
<script setup lang="ts"> import { useInstagram } from '@/composables/useInstagram' const { getRecentMedia, medias } = useInstagram() const handleSearch = async (keyword: string) => { await getRecentMedia(keyword) } </script> <template> <div class="wrapper"> <SearchInput class="mt-8" @search="handleSearch" /> <v-row class="my-8"> <v-col v-for="media in medias" :key="media.id" class="d-flex child-flex p-2" cols="4" > <v-card min-width="100%"> <v-img :src="media.media_url" aspect-ratio="1" cover class="align-end" > <template v-slot:placeholder> <v-row class="fill-height ma-0" align="center" justify="center" > <v-progress-circular indeterminate color="grey-lighten-5" ></v-progress-circular> </v-row> </template> </v-img> </v-card> </v-col> </v-row> </div> </template> <stype lang="scss" scoped> .wrapper { width: 65%; margin: 0 auto; } </stype>
完成形
まとめ
実際作ってみて、Nuxt3を使う以前にInstagram Graph APIの事前準備のところで手こずってしまい意外と時間かかってしまいました。
久々に個人開発をして楽しかったので良しとします笑
Nuxt3になってからTypeScriptのサポートが強化されたのはもちろんのこと、ビルドのスピードがより速くなったり、各ディレクトリのルールに沿ってファイルを作成することで自動インポートをしてくれたり、書き方がシンプルになったり、などなどNuxt2よりも爆速で開発ができるようになった印象です。
まだまだ、情報が少ない部分もありますがぜひNuxt3を使っていきましょう〜♪
最後に、今回作成したものはGitHubにあげています。
少しでも参考になれば幸いです。
株式会社フォトラクションでは一緒に働く仲間を募集しています