今回達成すること
クライアントからShopifyへAPIリクエストした場合、DeveloperToolにはアクセストークンが漏洩します。
今回はこの漏洩対応として、商品取得のリクエストAPIをサーバーから送信する設定を行います。
Shopify APIの前提条件
本記事を進めるにあたっての前提条件は以下の通りです。
1. nuxt-shopifyのインストール
この記事を経由していない方はnuxt-shopify
モジュールをインストールし、型を登録を行なってください。
% yarn add nuxt-shopify
tsconfig.json
{
// https://v3.nuxtjs.org/concepts/typescript
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"types": [
"nuxt-shopify"
]
}
}
2. nuxt-shopifyセットアップの削除
nuxt-shopify
のセットアップは使用しません。
全て削除してください。
nuxt.config.ts
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
// 以下、削除
// modules: [
// 'nuxt-shopify'
// ],
// Doc: https://nuxt-shopify-docs.vercel.app/
// shopify: {
// // shopify domain
// domain: process.env.SHOPIFY_DOMAIN,
// /*
// ストアフロントアクセストークン
// */
// storefrontAccessToken: process.env.SHOPIFY_ACCESS_TOKEN,
// /*
// unoptimized: true => SDKの拡張
// StorefrontAPIで使用できるすべてのフィールドがSDKを介して公開されるわけではありません。
// 最適化されていないバージョンのSDKを使用すると、独自のクエリを簡単に作成できます。
// これを行うには、UMDUnoptimizedビルドを使用します。
// Doc: https://shopify.github.io/js-buy-sdk/
// */
// unoptimized: false,
// // 翻訳されたコンテンツを返すための言語を設定する
// language: 'ja-JP',
// }
})
3. アクセストークンとショップドメインを.envに登録する
この記事を参考に、Shopifyの会員登録と商品登録を行なってください。
その後アクセストークンの取得と
4. NetlifyCLIのインストール
この記事ではデプロイまでを実行します。
その際にNetlifyCLIを使用するので、この記事を参考にインストールを行なってください。
Nuxt3のserver/apiを使用する
サーバーサイドからAPIリクエストを行うには、「server/api」ディレクトリ内のts
ファイルに処理を記述します。
API Routes
Nuxt は「~/server/api」 ディレクトリにある任意のファイルを自動的に読み込んで、APIエンドポイントを作成します。
各ファイルは、APIリクエストを処理するデフォルト関数をエクスポートする必要があります。
この関数は、プロミスまたはJSONデータを直接返すことができます(またはres.end()を使用します)。
Shopify APIに必要なファイル群を作成する
「server/api」ディレクトリの作成と、以下ファイル群を作成します。
% mkdir -p server/api/shopify && touch $_/{client.ts,get-product.ts,get-products.ts}
-
「shopify」ディレクトリ
-
client.ts ... Shopifyセットアップファイル -
get-product.ts ... 商品コンテンツ取得API -
get-product.ts ... 商品一覧取得API
-
shopify-buyのセットアップ
shopify-buy
を直接呼び出し、ドメイン、アクセストークン、コンテンツの言語を設定します。
server/api/shopify/client.ts
import Client from 'shopify-buy'
const client = Client.buildClient({
domain: process.env.SHOPIFY_DOMAIN,
storefrontAccessToken: process.env.SHOPIFY_ACCESS_TOKEN,
language: 'ja-JP'
})
export default client
shopify-buyの型を追加する
shopify-buy
の型が更新されていないのか、Shopifyプロダクトの一部プロパティが登録されていません。
そこでshopify-buy
の型を拡張し、今回使用する型を追加します。
まず、Shopifyの型定義ファイルを作成しましょう。
% mkdir types && touch $_/shopify.ts
image.whdth
image.height
descriptionHtml
types/shopify.ts
// type: node_modules/@types/shopify-buy/index.d.ts
import type { Product, Image } from 'shopify-buy'
// デフォルトの型に無いプロパティを追加
interface CustomImage extends Image {
width: number
height: number
}
export interface CustomProduct extends Product {
descriptionHtml: string
images: Array<CustomImage>
}
Shopifyの型の実態は「node_modules/@types/shopify-buy/index.d.ts」で確認できます。
タイプエラーが出た場合はプロパティが定義されているが確認してください。
商品一覧を取得するAPIを作成する
server/api/shopify/get-products.ts
import type { IncomingMessage, ServerResponse } from 'http'
import { CustomProduct } from '~/types/shopify'
import client from './client'
/**
* req は Node.js の http リクエストオブジェクト
* res は Node.js の http レスポンスオブジェクト
* next は、次のミドルウェアを起動するために呼び出す関数
* https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
*/
export default async (_req: IncomingMessage, _res: ServerResponse, _next: any) => {
console.log('/api/shopify/get-projects')
let products: CustomProduct[] = []
await client.product.fetchAll()
.then((response: CustomProduct[]) => (products = response))
return products
}
client.product.fetchAll()
... アクティブな商品一覧を取得するshopify-buy
のメソッド。
商品コンテンツを取得するAPIを作成する
server/api/shopify/get-product.ts
import type { IncomingMessage, ServerResponse } from 'http'
import { CustomProduct } from '~/types/shopify'
import client from './client'
import Url from 'url'
export default async (req: IncomingMessage, _res: ServerResponse, _next: any) => {
console.log('/api/shopify/get-project')
const currentUrl = Url.parse(req.url, true)
const productId = currentUrl.query.id
let product: CustomProduct | undefined
// productIdが存在 && 型stringの場合
if (productId && typeof productId === 'string') {
await client.product.fetch(productId)
.then((response: CustomProduct) => (product = response))
}
return product
}
Url.parse(req.url, true)
... 第一引数のURLをオブジェクトに変換する。URLパラメーターを取得する目的がある。client.product.fetch(id)
... 引数のidからShopifyの商品を取得するshopify-buy
のメソッド。
useFetchで商品一覧APIを呼び出す
「server/api」に作成したAPIはuseFetch('APIファイルパス')
で呼び出すことができます。
このAPIリクエストはサーバーサイドでしか実行されません。
商品一覧ページをまるっと書き換えましょう。
pages/products/index.vue
<template>
<NuxtLayout>
<ul
v-for="(product, i) in products"
:key="`product-${i}`"
>
<li>
<NuxtLink
:to="{ name: 'products-id', params: { id: product.id } }"
>
{{ product.title }}
{{ product.variants[0].price }}円
</NuxtLink>
<ul>
<li>
ID: {{ product.id }}
</li>
<li
v-for="(image, imageI) in product.images"
:key="`image-${imageI}`"
:style="{ display: 'inline-block', marginRight: '.5rem' }"
>
<img
:src="image.src"
:alt="`${product.title}-${imageI}`"
:width="image.width / 20"
:height="image.height / 20"
/>
</li>
<li
v-html="(product.descriptionHtml)"
/>
</ul>
</li>
</ul>
</NuxtLayout>
</template>
<script setup lang="ts">
/**
* server request only
* const { data: products } = await useAsyncData('get-products', () => {
* return $fetch('/api/shopify/get-products')
* })
* OR
* useFetch()
*
* client request
* const nuxtApp = useNuxtApp()
* const products = await nuxtApp.$shopify.product.fetchAll()
*/
const { data: products } = await useFetch('/api/shopify/get-products')
</script>
const { data: products }
...useFetch
で取得したデータはdata
プロパティに格納されている。このdata
プロパティに変数名を定義するには{ data: products }
とする。これでAPIで取得したデータをproducts
で扱うことができる。
useAsyncDataとuseFetchの違い
useAsyncData
とuseFetch
の違いは下記記事がとても綺麗にまとまっているので、ここでの説明は省略します。
useFetch()
とuseAsyncData()
のふたつの違いが気になりますがuseFetch()
はデータ取得に特化したuseAsyncData()
のラッパーでしかありません。両者に本質的な違いはなく
useAsyncData(key, () => $fetch(path), options)
のようにuseAsyncData()
の第2引数が() => $fetch()
になる場合はすべてuseFetch()
で構いません。データ取得以外のケースで非同期に処理をするケースというのは限定されることから
useFetch()
を使用するということで足りることが多いでしょう。
商品コンテンツAPIの呼び出し
商品コンテンツを表示するページからはuseFetch('/api/shopify/get-product')
を呼び出します。
pages/products/[id].vue
<template>
<NuxtLayout>
<NuxtLink
to="/products"
>
products
</NuxtLink>
<template v-if="product">
<ul>
<li>
{{ product.title }}
{{ product.variants[0].price }}円
<ul>
<li>
ID: {{ product.id }}
</li>
<li
v-for="(image, imageI) in product.images"
:key="`image-${imageI}`"
:style="{ display: 'inline-block', marginRight: '.5rem' }"
>
<img
:src="image.src"
:alt="`${product.title}-${imageI}`"
:width="image.width / 20"
:height="image.height / 20"
/>
</li>
<li
v-html="(product.descriptionHtml)"
/>
</ul>
</li>
</ul>
</template>
<template v-else>
<p>商品は見つかりませんでした。</p>
</template>
</NuxtLayout>
</template>
<script setup lang="ts">
const route = useRoute()
/**
* $fetch options params
* Doc: https://github.com/unjs/ohmyfetch#%EF%B8%8F-adding-params
*/
const { data: product } = await useFetch(
'/api/shopify/get-product',
{ params: route.params }
)
</script>
route.params
... ここには商品のIDが格納されている。
以上で、商品一覧と商品コンテンツをサーバーから呼び出すことができました。
useFetchのオプション
上のコードのように、第二引数の{ params }
に任意のパラメーターを渡すことができます。
useFetch
には、そのほかにもオプションが用意されています。
method
: リクエストメソッドを指定。
const { users } = await $fetch('/api/users', { method: 'POST', body: { some: 'json' } })
params
: パラメーターを渡す。
await $fetch('/movie?lang=en', { params: { id: 123 } })
headers
: リクエストヘッダの追加。
await $fetch('/movies', {
headers: {
Accept: 'application/json',
'Cache-Control': 'no-cache'
}
})
baseURL
: APIパスの前のURLを指定。
await $fetch('/config', { baseURL })
Netlifyにデプロイする
今回のShopify APIは、サーバーで稼働するためNetlifyサーバーに環境変数を登録する必要があります。
NetlifyCLI コマンドのenv:set
を使用してアクセストークンとドメインを追加します。
% yarn netlify env:set SHOPIFY_DOMAIN <ショップドメイン>.myshopify.com
% yarn netlify env:set SHOPIFY_ACCESS_TOKEN <アクセストークン>
# 登録の確認
% yarn netlify env:list
.---------------------------------------------------------.
| Environment variables |
|---------------------------------------------------------|
| Key | Value |
|----------------------|----------------------------------|
| SHOPIFY_DOMAIN | ....myshopify.com |
| SHOPIFY_ACCESS_TOKEN | a................ |
'---------------------------------------------------------'
間違って登録した場合は、env:set
で上書き、もしくはenv:unset
で削除できます。
% netlify env:unset SHOPIFY_DOMAIN
デプロイコマンドを実行しましょう。
% yarn netlify:deploy
デプロイコマンドを作成してない方は、下記script
コマンドを追加してください。
package.json
{
"scripts": {
"netlify:deploy": "netlify deploy --build --prod --open"
},
}
無事デプロイできました。
まとめ
今回はShopifyへサーバーからAPIリクエストを送るよう変更を行いました。
表示コンテンツは変わらないものの、サーバーサイドでの実行なのでアクセストークンの漏洩を防ぐことができます。
Nuxt3はすごいですね。Netlifyでもサーバーリクエストを実現できています。
もうNetlify Functionsは使用しなくてもNuxt3だけで事足りるかもしれません。
最後に、Nuxt3の新しいサーバーエンジンをご紹介して本記事を終わります。
Nuxt3の新しいサーバーエンジン
Nuxt3ではNitro Engine(ニトロ エンジン)という新しいサーバーエンジンが採用されています。
Nitroサーバーの基盤は、rollupとh3である:高いパフォーマンスと移植性のために構築された最小限のhttpフレームワークである。
本番環境では、アプリとサーバーを1つのユニバーサルな .output ディレクトリに構築します。この出力は軽量で、minifyされ、あらゆるNode.jsモジュール(polyfillsを除く)から削除されています。この出力は、Node.js、サーバーレス、Workers、エッジサイドレンダリング、または純粋に静的なものまで、JavaScriptをサポートするあらゆるシステムでデプロイできます。
引用: Nuxt3
Nitro Engineは、JAMstackに最適化されたスタンドアロン(他のリソースに依存せず単独で機能する)サーバーです。
スタンドアロンサーバー
Nitro は node_modules に依存しないスタンドアロンなサーバ dist を生成します。Nuxt 2のサーバはスタンドアロンではなく、nuxt startの実行(nuxt-startやnuxtディストリビューションを使用)やカスタムプログラムによる使用など、nuxt coreの一部が関与する必要があり、壊れやすくサーバレスやサービスワーカーの環境には適さないものでした。
このdistは、nuxt buildを実行すると、.outputディレクトリに生成されます。
出力はランタイムコードの両方と組み合わされ、あらゆる環境でNuxtサーバーを実行し(実験的なブラウザサービスワーカーを含む!)、静的ファイルを提供し、JAMstackのための真のハイブリッドフレームワークとなります。さらに、ネイティブストレージレイヤーが実装されており、マルチソース、ドライバ、ローカルアセットをサポートしています。
Nuxt2に存在したtarget
プロパティは、Nuxt3のドキュメントから削除されました。プロパティ自体は引き続き使用可能です。
もうtarget: 'static'
を定義しなくても、Nuxt3がJAMstackに最適化された静的ファイルを生成してくれるんですね。
引き続き
ssr
プロパティは存在します。