Astroで自前のエンドポイントを作成して、Vueコンポーネントでデータを取得する

## 前書き

こんにちは、Vue.jsとNuxt.jsでJavaScriptフレームワーク入門した者です
少し前にAstroにも手を出しまして、今は便利な世の中になったなあと実感しています

今回は、Astroのエンドポイントを実践します
内容としては、以下のページの内容です

https://docs.astro.build/ja/guides/endpoints/

## 背景情報

実践に移る前に、少し背景情報を書きます

そもそもやりたかったことは、「Astro Studioのデータベースに保存した情報をVueコンポーネントで表示する」ということでした

https://docs.astro.build/ja/guides/astro-db/

Astro StudioというAstroサイト向けのプラットフォームがありまして、これにデータベースがあります
そして、それはAstro DBから気軽にアクセスできるように設計されています

これは便利だと思って利用しようとしたのですが、Astro StudioのDBに保存したデータをどうやってVueコンポーネントに渡せばいいかわかりません

Astroにおいて、親コンポーネント（Astro）と子コンポーネント（Vue）間のデータのやりとりの適切な仕方がよくわからないということです

propsとか何かかなとは思いつつ、いまいちイメージがわきません
そのため、ひとまずエンドポイントを使うことにしました

```
Astro Studioのデータベース
↓　↑
Astroエンドポイント
↓　↑
Vueコンポーネント
```

こんな感じで、データのやりとりをおこないます

最終的には、Astro Studioではなく、CloudflareのD1を使うことにしたのですが、こんな背景があったのだなとご想像ください

今回の記事では、このうちの、AstroエンドポイントとVueコンポーネントの間のみを実装しますが、いずれ、AstroでCloudflare D1を利用するとかも気が向けば書きたいと思います

### 今回つくるもの

以下のようなものをつくることにしたいと思います

- 「タイトル」と「内容」をセットにしたコンテンツ（以降メモ）を渡すエンドポイントをつくる
- Vueコンポーネントからfetchでメモを取得する

そのうち、メモを保存する機能もつくりますが、今回は固定のメモを取得して、表示するだけです

## 本編

### プロローグ

まずは各種パッケージとかを準備します
便利なものが準備されているので、それらを活用します

```
npm create astro@latest
```
↓　実行結果
```
> npm create astro@latest
Need to install the following packages:
create-astro@4.8.0
Ok to proceed? (y) y


> npx
> create-astro


 astro   Launch sequence initiated.

   dir   Where should we create your new project?
         .

  tmpl   How would you like to start your new project?
         Empty

    ts   Do you plan to write TypeScript?
         Yes

   use   How strict should TypeScript be?
         Strict

  deps   Install dependencies?
         Yes
      ◼  Nice!
         Git has already been initialized

      ▲  error
         Error: Timeout
      ▲  error
         Dependencies failed to install, please run npm install to install them manually after setup.
      ✔  Project initialized!    
         ■ Template copied       
         ■ TypeScript customized 
         ■ Dependencies installed

  next   Liftoff confirmed. Explore your project!
 Run npm run dev to start the dev server. CTRL+C to stop.
 Add frameworks like react or tailwind using astro add.

 Stuck? Join us at https://astro.build/chat
```

パッケージのインストールに失敗したらしいので、もう一回実行します

```
> npm install

up to date, audited 457 packages in 3s

178 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
```

はい、OKです
では、ひとまず動かしてみます

```
npm run dev
```
↓　実行結果
```
> npm run dev

> dev
> astro dev

19:32:54 [types] Added src/env.d.ts type declarations.

 astro  v4.11.0 ready in 605 ms

┃ Local    http://localhost:4321/
┃ Network  use --host to expose

19:32:55 watching for file changes...
```

で、`http://localhost:4321/`にアクセスします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821654/6109672a-455e-de54-51af-9bfe8328a34a.png)

大丈夫そうです

### エンドポイント作成

[https://docs.astro.build/ja/guides/endpoints/#静的ファイルのエンドポイント](https://docs.astro.build/ja/guides/endpoints/#静的ファイルのエンドポイント)によると、

> カスタムエンドポイントを作成するには、.jsまたは.tsファイルを/pagesディレクトリに追加してください。.jsまたは.tsの拡張子はビルドプロセス中に削除されるので、ファイル名には作成したいデータの拡張子を含める必要があります。たとえば、src/pages/data.json.tsは、ビルドすると/data.jsonエンドポイントとなります。

とのことなので、今回はエンドポイントのアドレスを`http://localhost:4321/v1/memo`とするため、`src/pages/v1/memo.ts`というファイルを作成します

ソースコードはドキュメントに則って、以下のようにします

```javascript:src/pages/v1/memo.ts
const example = [
    { id: 1, title: "タイトル1", content: "コンテント1" },
    { id: 2, title: "タイトル2", content: "コンテント2" },
]

export async function GET() {
    return new Response(
        JSON.stringify({
            body: { memos: example }
        })
    )
}
```

この状態で（`npm run dev`して）、`http://localhost:4321/v1/memo`にアクセスしてみます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821654/c06e858f-97fb-d2c0-1896-026e6a499c63.png)

OKですね

次に、Vueコンポーネントでエンドポイントからデータを取得します

### Vueコンポーネントでエンドポイントにアクセスする

Vueコンポーネントを使えるように、インテグレーションを追加します

```
npx astro add @astrojs/vue
```
↓　実行結果
```
> npx astro add @astrojs/vue
✔ Resolving packages...

  Astro will run the following command:
  If you skip this step, you can always run it yourself later

 ╭──────────────────────────────────────────────╮
 │ npm install @astrojs/vue@^4.5.0 vue@^3.4.29  │
 ╰──────────────────────────────────────────────╯

√ Continue? ... yes
✔ Installing dependencies...

  Astro will make the following changes to your config file:

 ╭ astro.config.mjs ─────────────────────────────╮
 │ import { defineConfig } from 'astro/config';  │
 │                                               │
 │ import vue from "@astrojs/vue";               │
 │                                               │
 │ // https://astro.build/config                 │
 │ export default defineConfig({                 │
 │   integrations: [vue()]                       │
 │ });                                           │
 ╰───────────────────────────────────────────────╯

√ Continue? ... yes

   success  Added the following integration to your project:
  - @astrojs/vue
```

VueコンポーネントはComposition APIで実装します
エラー処理はしていません

```javascript:src/components/DisplayList.vue
<script setup>
import { ref, onMounted, computed } from 'vue';

const memos = ref(0);

onMounted(() => {
    getMemos();
});

async function getMemos() {
    const response = await fetch('v1/memo');
    const json = await response.json();

    if (json.body.memos) {
        memos.value = json.body.memos;
    }
}
</script>

<template>
    <div>
        <dl>
            <template v-for="memo in memos">
                <dt>{{ memo.id }}:{{ memo.title }}</dt>
                <dd>{{ memo.content }}</dd>
            </template>
        </dl>
    </div>
</template>
```

### AstroコンポーネントでVueコンポーネント呼び出し

メインのAstroコンポーネントにDisplayListコンポーネントを呼び出すコードを追加します

このとき、https://docs.astro.build/ja/guides/framework-components/#インタラクティブなコンポーネント にあるように、`client:load`を追加する必要があると思います、多分

```html:src/pages/index.astro
---
import DisplayList from "../compoents/DisplayList.vue"
---
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
		<meta name="viewport" content="width=device-width" />
		<meta name="generator" content={Astro.generator} />
		<title>Astro</title>
	</head>
	<body>
		<h1>Astro</h1>
		<DisplayList client:load/>
	</body>
</html>
```

後、エンドポイントを使うので、`astro.config.mjs`に` output: 'hybrid'`を追加しておきます（これもわたしには理解できていないおまじないです）
https://docs.astro.build/ja/basics/rendering-modes/#オンデマンドレンダリング

```js:astro.config.mjs
import { defineConfig } from 'astro/config';

import vue from "@astrojs/vue";

// https://astro.build/config
export default defineConfig({
  integrations: [vue()],
  output: 'hybrid',
});
```

そして、動作確認です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821654/346cc9ce-01de-3568-fb26-541ed26d95f5.png)

よさそうですね

## まとめ

以上、ドキュメント通りに実践してみました

今後、これをベースにして、メモの追加機能を実装したり、Cloudflare D1と接続してみたりしたいと思います

後、Lucia Authという認証ライブラリを使って、GitHubアカウントによるログイン機能も試したので、それも追々まとめていきます

ここまで、ご清聴ありがとうございました

## おまけメモ

```json:package.json
{
  "name": "",
  "type": "module",
  "version": "0.0.1",
  "scripts": {
    "dev": "astro dev",
    "start": "astro dev",
    "build": "astro check && astro build",
    "preview": "astro preview",
    "astro": "astro"
  },
  "dependencies": {
    "@astrojs/check": "^0.7.0",
    "@astrojs/vue": "^4.5.0",
    "astro": "^4.11.0",
    "typescript": "^5.5.2",
    "vue": "^3.4.29"
  }
}
```