Closed azain4645 closed 2 years ago
@azain4645 結論を先に述べると、CORSエラーが発生しないよう問い合わせを行いたい場合には、「サーバ側でコールする」のが妥当ですね!
CORSエラーとは何か、という部分の理解が必要なのでざっくりご説明しますね!
オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) とは、Webページが自身とは異なる オリジン のURLにアクセスを行うことができる機能ですね!
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS
上記の「CORS」の機能は、サーバ側で設定を行うものなのですが、
特にサーバ側でCORSの設定がされていない場合、ブラウザは「異なるオリジンのURLと通信できない」仕様になっています。
なので、そういったCORS未設定の Web-API などにブラウザから通信を試みた場合、ブラウザの仕様に従って、アクセスを拒絶されるのが 「CORSエラー」 です。
ブラウザの仕組みであり、CORSを許可するには Web-API 提供元のサーバ側での設定が必要な以上、第三者が CORS未許可の Web-API を 「ブラウザから」 コールすることはできません。
つまり、逆に言えば「ブラウザ以外からであれば」CORSの仕組みは機能しないので、コールできます。
というわけで、話をNuxt3アプリケーションに戻すと、現状のコードではフロントエンドから直接 useFetch で Web-API から情報を取得しようとされていますが、
これを下記のような構成に直すと、うまく動くようになります(サーバ側の機能を使うので、ビルドモードは SSGではなく、SSR である必要があります)
/server/api/任意の名前.ts
で 「Web-API に問い合わせを行う Nuxt3アプリケーション内のServer Routes」を実装するpages/xxxx.tsx
では、直接 Web-API に問い合わせを行うのではなく、上記で実装した Server Routes への useFetch や $fetch による問い合わせを行うこうすると、あくまでフロントエンドが通信しているのは自分自身が所属する Nuxt3 アプリケーションのサーバなので、CORS未許可のWeb-APIであっても問い合わせを行うことができます!
なお Server Routes server/api/任意の名前.ts
のサーバ処理は、URL /api/任意の名前
で公開されますので、 $fetch('/api/任意の名前')
や、 useFetch('/api/任意の名前')
でフロントエンドから問い合わせできます!
※ Nuxt3 Server Routes のドキュメント https://v3.nuxtjs.org/guide/features/server-routes/
origin === protocol + domain + port です!
http://localhost:8080/hoge/fuge
というURLがあるとしたら、このうちの http://localhost:8080
がオリジンですね!
このオリジンの文字列が違う場合には、CORS設定がない限り、ブラウザから直接リソースを取得することはできません。
※ MDNのオリジン解説ドキュメント https://developer.mozilla.org/ja/docs/Glossary/Origin
Nuxt3 アプリケーション内で「簡易的なWeb-API を作成&公開できる機能」です。 便利なのでよく使いますね
(Server Routes を AWS Lambda に乗せて、フロントエンドを Amazon S3 に乗せることで SSR版Nuxt3アプリケーションをAWS上でホストしたりします)
実装方法は上記解説中に記載した公式ドキュメントをご覧ください!
ありがとうございます。
これを下記のような構成に直すと、うまく動くようになります(サーバ側の機能を使うので、ビルドモードは SSGではなく、SSR である必要があります)
とりあえずお聞きしますが、今回のAPIがCORS未許可なので、サーバ側で取得する必要があり、将来的にBaaSを利用する場合は、自分で設定ができるので、SSGでも問題ないですか?
それとも教えて頂いたように
(Server Routes を AWS Lambda に乗せて、フロントエンドを Amazon S3 に乗せることで SSR版Nuxt3アプリケーションをAWS上でホストしたりします)
という形にするのが一般的ですか?
@azain4645
とりあえずお聞きしますが、今回のAPIがCORS未許可なので、サーバ側で取得する必要があり、将来的にBaaSを利用する場合は、自分で設定ができるので、SSGでも問題ないですか?
お、今回ご利用された Web-API はあくまで今回だけのもので、将来的には BaaS で自分用の Web-API を立ち上げられるという話ですかね? そうですね、そういうことでしたら自分自身で CORSの設定をする(あるいはそもそもWeb-APIと同じオリジンでNuxt3 をホスティングする)ことができるはずなので、SSGでも問題ないですよ!
なおSSRとSSGについてどちらが一般的ということは特になく、システムが実現したい要件によって、どちらにすべきかは都度変わりますね!
ですので、azain4645 さんがその時開発したいシステムの構成によって、このあたりは都度良さそうな方を選んでいくといいですよ!
公式のドキュメントなど見ながらserver/api/ を用意して、 http://localhost:3000/api/hello 自体でAPIのデータを返却するところまでは出来ました。
ただ、page側で受け取ることがわかりませんでした (ちょっとネット上でもサンプル少ないですね)
元の記事では const response = await $axios.$get<Article[]>(url) state.articles = response というようにArticleの型で取得しているようですが、このあたりも含めて教えていただけたら幸いです。
宜しくお願いします
@azain4645
結論としてコードから載せるとこんな感じですね!
type Article = {
title: string
likedCount: number
publishedAt: string
}
export default defineEventHandler(async (e) => {
// 外部とのやり取りなら型推論が効かないので genericsに型を明示してやる
const result = await $fetch<Article[]>(`https://zenn-api.netlify.app/.netlify/functions/trendTech`);
return result;
});
<script setup lang="ts">
type Article = {
title: string
likedCount: number // スペルミス訂正
publishedAt: string // スペルミス訂正
}
type State = {
articles: Article[]
}
const headers = [
{ text: '記事名', value: 'title' },
{ text: 'いいね数', value: 'likedCount' },
{ text: '公開日時', value: 'publishedAt' },
]
const state = ref<State>({
articles: [],
})
const getZennArticles = async () => {
// 型注釈やgenericsを使わずとも、/api/getArticle の応答型が自動で型推論される
// (なのでこれだけで、resultはArticle[]であるものとVSCodeの側でちゃんと表示される)
const result = await $fetch("/api/getArticle")
console.log({result})
state.value.articles = result;
}
onMounted(() => {
getZennArticles()
})
</script>
<template>
<pre>{{
state.articles.length === 0 ?
'loading...'
: JSON.stringify(state.articles, null, 2)
}}</pre>
</template>
@azain4645
Nuxt3公式ドキュメントに、
This composable provides a convenient wrapper around useAsyncData and $fetch. It automatically generates a key based on URL and fetch options, as well as infers API response type. (これはuseAsyncDataと$fetch を使いやすくラップした関数です。URLとパラメータに基づいてキーを自動的に生成し、APIの応答の型を推測します)
https://v3.nuxtjs.org/guide/features/data-fetching/
とあるように、useFetch, useAsyncData, $fetch などは「URLとパラメータに基づいて応答を型推論する」ようになっています。
ですので、Server Routes /server/api/xxx.ts
で自らNuxt3アプリケーション内に定義した簡易Web-APIであれば、自動的に応答が型推論されますので、特に明示的な型の記述は不要です。
一方で、上記コードの /server/api/getArticle.ts
で使っているような Nuxt3アプリケーションの外部への問い合わせの場合には、型推論が効きません。
ですので、Fetch系メソッドの generics に型を明示してやることで、応答の型を指定してやることができます。
const result = await $fetch<Article[]>(`https://zenn-api.netlify.app/.netlify/functions/trendTech`);
@azain4645
ちなみに今回に限らず、TypeScriptで「このメソッドはどうにかして型を補足できそうだ...」と思えるコードがあれば、ひとまずそのメソッドの型情報を見るといいですよ!
例えば $fetch だったら、VSCode 上で $fetch の上にカーソルを置くと、
$Fetch
<unknown, "https://zenn-api.netlify.app/.netlify/functions/trendTech">(request: "https://zenn-api.netlify.app/.netlify/functions/trendTech", opts?: FetchOptions<ResponseType>) => Promise<...>
というような情報がポップアップされます。
この型情報を見れば、
<unknown, "https://zenn-api.netlify.app/.netlify/functions/trendTech">
と書いてあるのでgenericsで型を補足出来るかもしれないと推測できます。
実際に、今回書いたコードのように $fetch をgenericsで <Article[]>
にしてやると、
表示される型情報が次のように変わります。
var $fetch: $Fetch
<Article[], NitroFetchRequest>(request: NitroFetchRequest, opts?: FetchOptions<ResponseType>) => Promise<...>
...と、まあこのような形でTypeScriptならコード見るだけで使い方を推測出来るようになっているので、 もし型の補足が出来そうだと感じたら、ひとまずそのメソッドの型情報を見ると分かりやすいですよ!
余談ですが、ここスペルミスから上手に値を連携出来ていなかったのでお知らせです!
type Article = {
title: string
- linkedCount: number
- publichedAt: string
+ likedCount: number // スペルミス訂正
+ publishedAt: string // スペルミス訂正
}
(おそらく @azain4645 さんの方で pages/index.vue
の useFetch
がうまく動いていなかったのだとしたら、それはこのスペルミスが原因だと思います)
@azain4645 さらに余談ですが、私の上記動作確認コードでは azainさんのコードをこのように書き換えさせていただきましたが、
- test
- <!-- <v-data-table
- :headers="headers"
- :items="state.value.articles"
- :items-per-page="5"
- class="elevation-1"
- >
- </v-data-table> -->
+ <pre>{{
+ state.articles.length === 0 ?
+ 'loading...'
+ : JSON.stringify(state.articles, null, 2)
+ }}</pre>
そもそも v-data-table
は、azainさんがインストールされた "vuetify": "^3.0.0-beta.5"
には存在しないコンポーネントですので、ここは Vuetify + Table で表示させたいなら違うコンポーネントを使うのが妥当です!
※ v-data-table は Vuetify2 のコンポーネントで、Vuetify3 には存在しない状態です。Vuetify3 のテーブルコンポーネントの公式ドキュメントを元に、Vuetify3 としての書き方への修正が必要ですね!
nuxt3+vuetifyで、zennの非公式APIからデータを取得して表示するサンプルを見つけたので、参考にして実装しています。 元の記事では、script setup 以前の内容でしたので、部分的に変更しながら進めています。
https://zenn.dev/satonopan/articles/eca27c1b4f0b93
問題は、元はaxiosを使っており、useFetchに書き換えたのですが、CORSエラーになりデータが取得出来ていないようです。 あまりわかっていない部分もあるんですが、こちら側の設定の問題でしょうか?
https://github.com/azain4645/zennThread/blob/e4435d18b6885eb6ba4e8c4d13a9c72135cc0883/pages/index.vue#L23-L28
また、元の記事では const response = await $axios.$get<Article[]>(url) state.articles = response というようにArticleの型で取得しているようですが、同じように書くことが出来ますか?
すみませんがお願いします