Vite × preact × Typescript × Prisma × PlanetScale × Netlify  入門

PlanetScale を触ってみたいと思っていたので、簡易的なアプリを作成しました。
Vite、preact、も以前から気になっていたので、参考になれば嬉しいです。

## 構成
- ビルドツール
    - vite
- データベース
    - PlanetScale
- サーバ
    - Netlify
- アプリケーション
    - preact, Typescript, Prisma


## 始める前に結論
- 無料枠が大きいので個人開発で遊ぶのにも十分だと思う
- UXが最高。ほとんどのプログラマーがgithubユーザーだと思うので、直感的に操作することが可能
- mysqlで抱えていた、本番DBと開発DBの情報に差分が発生して意図しないバグを生じてしまう問題を上手く解決してくれそう（上記に記載したように、githubのようなユーザー体験が提供されているためschema変更の`revart`なんかも可能）
## 始める前の準備
PlanetScale と Netlify はそれぞれ cli が用意されているのでインストールしておく
```terminal
npm install netlify-cli -g
netlify // インストールされているか確認

⬥ Netlify CLI
Read the docs: https://www.netlify.com/docs/cli
Support and bugs: https://github.com/netlify/cli/issues

VERSION
  netlify-cli/9.16.6 darwin-x64 node-v14.17.0
```

## Vite プロジェクト作成
適当なディレクトリを作成
```terminal
mkdir ディレクトリ名
cd ディレクトリ名
npm init vite@latest .
```
プロジェクト名等々、何を利用するか問われるので以下の項目を入力・選択
```terminal
npm init vite@latest
npx: 6個のパッケージを3.977秒でインストールしました。
✔ Project name: … プロジェクト名
✔ Select a framework: › preact
✔ Select a variant: › preact-ts

cd プロジェクト名
npm i
```
プロジェクト作成完了
ローカルサーバを走らせる
```terminal
netlify dev

◈ Netlify Dev ◈
◈ Ignored general context env var: LANG (defined in process)
◈ Starting Netlify Dev with Vite

   ┌─────────────────────────────────────────────────┐
   │                                                 │
   │   ◈ Server now ready on http://localhost:8888   │
   │                                                 │
   └─────────────────────────────────────────────────┘
```
補足: `netlify dev`コマンドに関しては以下を参照
[Run the entire Netlify platform right from your terminal](https://www.netlify.com/products/cli/)

`http://localhost:8888`でプロジェクトが正常に立ち上がっているか確認
![スクリーンショット 2022-04-25 14.07.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/dc4e59bd-f442-1042-d4d1-da07ae4528f9.png)

## PlanetScale の設定
補足: PlanetScale のアカウントをまだ作成していなければ、以下を参照
[PlanetScaleというサーバレスDBが凄く勢いのあるサービスらしいのでQuick Startやってみた](https://qiita.com/tak001/items/cfbaa9dcb542929ff235#db%E5%90%8D%E3%81%A8region%E3%82%92%E6%B1%BA%E3%82%81%E3%82%8B)
pscale cli 取得
```terminal
$ npm i scale -g
$ which sclae
```
https://planetscale.com/cli

ターミナルから PlanetScale へ login
```terminal
$ pscale auth login
 Confirmation Code: ここに認証用コードが表示される
```
ブラウザで planetscale 認証ページが自動で開かれるので、ターミナル上に出力される認証用コードと一致しているか確認。問題なければブラウザ上に表示されている、「Confirme code」ボタンをクリック

開発用ブランチを作成する
```terminal
$ pscale branch create データベース名 dev // devは任意のブランチ名
```
shadowブランチを作成する
※ このブランチはPrismaのmigrations用に使われる
```terminal
$ pscale branch create データベース名 shadow // devは任意のブランチ名
```
正常に上記設定が行われているか確認
ローカルからデータベースへ接続
```terminal
$ pscale connect データベース名 dev[ブランチ名] --port 3309
$ pscale connect データベース名 shadow[ブランチ名] --port 3310
```
エディタで本プロジェクトコードを展開して`.env`ファイルを追加
追加した`.env`ファイルに以下記述
```terminal
DATABASE_URL="mysql://root@127.0.0.1:3309/データベース名"
SHADOW_DATABASE_URL="mysql://root@127.0.0.1:3310/データベース名"
```

## Prisma の設定
prisma をプロジェクトに追加
```terminal
npm i -D prisma
npm i @prisma/client
```
schemaファイルを生成

```terminal
npx prisma init
```
`schema.prisma`を以下のように修正

```javascript
generator client {
  provider = "prisma-client-js"
  previewFeatures = ["referentialIntegrity"]
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
  shadowDatabaseUrl      = env("SHADOW_DATABASE_URL")
  referentialIntegrity = "prisma"
}
```
アプリ用モデルを`schema.prisma`に追加する
```javascript
generator client {
  provider = "prisma-client-js"
  previewFeatures = ["referentialIntegrity"]
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
  shadowDatabaseUrl      = env("SHADOW_DATABASE_URL")
  referentialIntegrity = "prisma"
}

model Post {
  id Int @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now())
  title String @db.VarChar(255)
  content String
}

```
上記で定義した schema を基にテーブルを作成
```terminal
$ npx prisma migrate dev
```
新しいマイグレーション name を入力
```terminal
Enter a name for the new migration: ... init
```
試験用データをテーブルに追加する
```terminal
$ npx prisma studio
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Prisma Studio is up on http://localhost:5555
```
ブラウザで`http://localhost:5555`を開く
![スクリーンショット 2022-04-25 21.44.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/e98fc7b4-d5c5-9c87-88a4-5ecb5b3df302.png)
schema ファイルを基に prisma client を生成
```terminal
$ Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma

✔ Generated Prisma Client (3.12.0 | library) to ./node_modules/@prisma/client in 70ms
You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
```
`npx prisma generate`コマンドはスキーマを取得し、typescript定義を持つprismaクライアントを作成して、データベースとのやり取りに必要なものを自動で準備してくれる

## serverless functionを定義
プロジェクトのルートディレクトリに`netlify/functions/posts.ts`を作成する.
ファイル内は以下のような構成.
処理の内容は post した情報をDBから取得して返すだけのシンプルなもの.
```typescript
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

export async function handler() {
  try {
    const posts = await prisma.post.findMany();
    return {
      statusCode: 200,
      header: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(posts),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify(error),
    };
  }
}

```

ちなみに、Netlify の Netlify function とは Netlify が提供しているアドオンの1つで、サーバーサイドの機能を簡単に公開できるサービスです。AWS Lambda をラップして使いやすいように提供されており、 AWSにアカウントを用意する必要もありません。NetlifyのプロジェクトとGitリポジトリを連携するだけでOK。Netlifyが代わりにデプロイをしてくれる上に、HTTPで呼び出せる機能がすぐに用意できる代物。

上記コードが正常に機能するかテストする
ローカルサーバを起動（もし起動していなければ）
```
$ netlify dev
◈ Netlify Dev ◈
◈ Ignored build settings env var: DATABASE_URL (defined in .env file)
◈ Injected .env file env var: DATABASE_URL
◈ Ignored general context env var: LANG (defined in process)
◈ Injected .env file env var: SHADOW_DATABASE_URL
◈ Loaded function post.
◈ Loaded function posts.
◈ Functions server is listening on 62987
◈ Starting Netlify Dev with Vite

> prisma-serverless@0.0.0 dev /Users/ryoma_kishimoto/Desktop/prisma-serverless-planetscale-netlify/prisma-serverless
> vite


  vite v2.9.5 dev server running at:

  > Local: http://localhost:3000/
  > Network: use `--host` to expose

  ready in 540ms.


   ┌─────────────────────────────────────────────────┐
   │                                                 │
   │   ◈ Server now ready on http://localhost:8888   │
   │                                                 │
   └─────────────────────────────────────────────────┘
```
posts を返すか確認
```
http://localhost:8888/.netlify/functions/posts
```
レスポンスが確認できたらOK
![スクリーンショット 2022-05-09 19.22.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/a067a4aa-4c10-f7df-9947-b7943ad54316.png)

次は実際にデータを post する処理をプロジェクトのルートディレクトリ（`netlify/functions/post.ts`）に作成する.

```typescript
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

export async function handler(event) {
  const { title, content } = JSON.parse(event.body);
  try {
    await prisma.post.create({
      data: { title, content },
    });
    return {
      statusCode: 200,
      body: 'post created',
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify(error),
    };
  }
}

```
正常に起動するか確認する
VSCodeの拡張機能で`Thunder Client`という優れものがあるので、おすすめです.
https://www.thunderclient.com/

使い方もとてもシンプル
![スクリーンショット 2022-05-09 19.34.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/3e8fbdb7-4ad6-b689-44e0-a6573fa1c82a.png)


## ブラウザからのPOST機能実装
以下のコードをそのまま`src/app.tsx`へ`copy & paste`しちゃってOK

```typescript:src/app.tsx
import { useState, useEffect } from 'preact/hooks';

type Post = {
  id: number;
  title: string;
  content: string;
  createdAt: Date;
  updatedAt: Date;
};

export function App() {
  const [loadPosts, setLoadPost] = useState(true);
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    async function load() {
      if (!loadPosts) {
        return;
      }
      const allPosts = await fetch('/.netlify/functions/posts').then((res) =>
        res.json()
      );
      setPosts(allPosts);
      setLoadPost(false);
    }
    load();
  }, [loadPosts]);

  async function handleSubmit(event: any) {
    event.preventDefault();

    await fetch('/.netlify/functions/post', {
      method: 'POST',
      body: JSON.stringify({ title, content }),
    });
    setTitle('');
    setContent('');
    setLoadPost(true);
  }

  return (
    <>
      <h1>投稿画面</h1>
      <ul>
        {posts.map((post: Post) => (
          <li key={post.id}>
            <h3>{post.title}</h3>
            <p>Created {new Date(post.createdAt).toLocaleString()}</p>
            <p>Updated {new Date(post.updatedAt).toLocaleString()}</p>
          </li>
        ))}
      </ul>
      <h2>投稿しよう</h2>
      <form onSubmit={handleSubmit}>
        <label htmlFor='title'>Title</label>
        <input
          type='text'
          id='title'
          name='title'
          value={title}
          onChange={(e) => setTitle((e.target as HTMLInputElement).value)}
        />

        <label htmlFor='content'>content</label>
        <input
          type='content'
          id='content'
          name='content'
          value={content}
          onChange={(e) => setContent((e.target as HTMLInputElement).value)}
        />

        <button type='submit'>Save</button>
      </form>
    </>
  );
}

```
一応、cssも記述.
```css:src/index.css
html,
body {
  font-family: 'Helvetica Neue', arial, sans-serif;
}
button,
label {
  display: block;
  margin-top: 1rem;
}

```

以下画面がブラウザ上で確認できるはずなので正常に「投稿→投稿されたPOSTが表示」されるか挙動チェック

![スクリーンショット 2022-05-09 22.09.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/d858a33a-255c-09d7-7296-bc1f79369d00.png)

## PlanetScaleのProduction環境DBの準備
PlanetScale のコンソール画面上から`main`ブランチを選択
![スクリーンショット 2022-05-09 22.19.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/0cabc79c-0865-0603-8fe3-4e053088a320.png)

`main`ブランチを`production`用としてpromote
![スクリーンショット 2022-05-09 22.22.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/a8f5de85-0d8d-5fc4-3793-aec6b3e87869.png)
![スクリーンショット 2022-05-09 22.26.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/729f68ef-2e88-b316-1854-d8b9fe4d058f.png)
任意のブランチを`main`ブランチに`deploy`するために、`deploy request`を上げる.（`git`の`pull request`に近いイメージ）
ターミナルから以下コマンドを叩く
```terminal
pscale deploy-request create データベース名 dev // devは任意のブランチ名
```
PlanetScaleのコンソール画面上にある、「Deploy requests」を選択すると、`deploy request`が確認できる
![スクリーンショット 2022-05-09 22.42.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/6417701d-24ed-ab4a-aba1-71734471ac58.png)
`summary`タブから「Add changes to deploy queue」ボタンをクリック
![スクリーンショット 2022-05-09 22.46.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/3c90a07c-95a7-2989-6717-75a0b611b8cc.png)

## Netlifyへアプリケーションをデプロイ
丁寧にまとめられている、以下記事を参考にして下さい
https://qiita.com/suin/items/743fe6252ad8af425c5e

「Site settings」をクリックし、画面左に表示されている「Build & deploy」タブを選択
![スクリーンショット 2022-05-09 22.58.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/a62baf3e-1bb7-3421-5802-1d1c15c34cc7.png)
![スクリーンショット 2022-05-09 23.00.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/ce59619d-ef14-8f77-2b91-5d7b34c7e15b.png)
「Build settings」項目で以下のように設定し、下部の「save」ボタンをクリック
![スクリーンショット 2022-05-09 23.02.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/88bb82be-aedf-19cd-fc30-105c54b0f421.png)
PlanetScaleからDBへのURLを取得する
コンソール画面上の「overview」タブを選択し、画面右の「connect」ボタンをクリック
![スクリーンショット 2022-05-09 23.05.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/1b0a4485-a043-7d7d-2ce1-9d14ca4aee18.png)
「connect with」ドロップダウンメニューから「Prisma」を選択し、画面右のコピーアイコンをクリックしたら適当なメモ帳等にペーストしておく
![スクリーンショット 2022-05-09 23.09.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/ea3c97bc-2305-323d-423f-e9ab5edaa7b3.png)


Netlifyへ戻り、「Site settings」をクリック、画面左に表示されている「Build & deploy」タブを選択して、以下スクショのように設定（`value`には先ほどコピペした、DATABASE_URL情報のここから→`mysql://~`をペーストする）
![スクリーンショット 2022-05-09 23.13.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/39521854-3865-4585-e766-132815b7243c.png)

これで終了. もし正常にdeployされていない場合は、「Deploy」タブから「Trigger deploy」ボタンをクリックして再度、deployを行なって下さい。
![スクリーンショット 2022-05-09 23.19.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/6b67e4d6-032e-901d-7b68-93e38884889e.png)

以下スクショ内のリンクをクリックすると、Netlify にデプロイされたページへ遷移することができます
![スクリーンショット 2022-05-16 17.43.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254495/26e56862-5a37-9d60-608a-7141b65270cf.png)