🙌

Next.js Supabase Prisma を使用してアプリを作る 4/6 覚書

2022/06/10に公開

https://themodern.dev/courses/build-a-fullstack-app-with-nextjs-supabase-and-prisma-322389284337222224
気になったところだけ書くので実際にされる方は↑から進めてください。

Next.js とのやりとり

SSRを使用した Next.js のデータクエリ

npm run dev
さっき入れたレコードが入ってないことを確認。

なぜ入ってないのか。

index.js を見るとローカルの data.json からデータを取得していることがわかる。

json にデータを渡すために SSR を使用する。
SSR: サーバー側からリクエストごとにページを事前にレンダリングする機能

既存のコードにSSR機能をもたせる。

index.js
// 既存のコード.
export default function Home() {
  return (
    <Layout>
      <h1 className="text-xl font-medium text-gray-800">
        Top-rated places to stay
      </h1>
      <p className="text-gray-500">
        Explore some of the best places in the world
      </p>
      <div className="mt-8">
        <Grid homes={homes} />
      </div>
    </Layout>
  );
}

追加

index.js
// インスタンス化
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

export async function getServerSideProps() {
// この関数内で prisma を使用して Supabase からデータをフェッチ.
  const homes = await prisma.home.findMany();
  return {
    props: {
      homes: JSON.parse(JSON.stringify(homes)),
    }
  };
}
// homesを渡す
export default function Home({ homes = [] }) {
  return (
    <Layout>
      <h1 className="text-xl font-medium text-gray-800">
        Top-rated places to stay
      </h1>
      <p className="text-gray-500">
        Explore some of the best places in the world
      </p>
      <div className="mt-8">
        <Grid homes={homes} />
      </div>
    </Layout>
  );
}

PrismaClient を使用して Next.js からデータを作成

Prisma Studio から作成したが、アプリからも登録できるようにしていく。

フォームを追加して新しいレコードを作成

フォーム作成。

create.js
import Layout from '@/components/Layout';
import ListingForm from '@/components/ListingForm';

const Create = () => {
  const addHome = () => null;

  return (
    <Layout>
      <div className="max-w-screen-sm mx-auto">
        <h1 className="text-xl font-medium text-gray-800">List your home</h1>
        <p className="text-gray-500">
          Fill out the form below to list a new home.
        </p>
        <div className="mt-8">
          <ListingForm
            buttonText="Add home"
            redirectPath="/"
            onSubmit={addHome}
          />
        </div>
      </div>
    </Layout>
  );
};

export default Create;

Form とか作ったことないので ListingForm も確認しておく.
formik でフォーム作成、 yup でバリデーションの組み合わせがいいらしい。
https://zenn.dev/msksgm/articles/20211121-react-form-formik-yup

API エンドポイントを作成

エンドポイント: APIにアクセスするためのURIこのこと

Next.js プロジェクト内にAPIエンドポイントを作成し、
DBにレコードを作成するためのHTTPリクエストを処理していく

APIエンドポイントは pages/api 内にファイルを配置する

pages
   └ api
       └ homes.js

APIルートでリクエストを処理するには、パラメーターを受け取る関数をエクスポートする必要がある。
リクエストハンドラーとか呼ばれてるらしい。

export default async function handler(req, res) {}

ついでにAPIルートのドキュメントも読んでおく: https://nextjs-ja-translation-docs.vercel.app/docs/api-routes/introduction

  • req: http.IncomingMessageのインスタンスと、組み込みミドルウェア
    • レスポンスステータス、ヘッダー、データ等にアクセスするために使用
    • 受け取ったリクエストをパースする
  • res: http.ServerResponseのインスタンスと、ヘルパー関数。
    • HTTPサーバーによって内部的に作成される
    • res.status など

POST リクエスト処理

HTTPリクエストのメソッドを確認して処理を分ける。

homes.js
export default async function handler(req, res) {
  // POSTメソッドであれば.
  if (req.method === 'POST') {
    // TODO
  }
  // それ以外.
  else {
    res.setHeader('Allow', ['POST']);
    res
      .status(405)
      .json({ message: `HTTP method ${req.method} is not supported.` });
  }
}

PrismaClient で新しいレコードを作成

Prisma Client を使用してHTTPリクエストから受け取ったデータで、新しいレコードを作成していく。
req.body からプロパティ取得。
create メソッドを使用してクエリ作成。
クエリは新しく生成された home レコードを返却するため、クライアンとに情報を返す。
res.status(200).json(home);

APIエンドポイントの呼び出し

axios が使えるらしい。
npm i axios
エンドポイントからデータ取得.
const addHome = data => axios.post('/api/homes', data);

Next.js と DB のやりとりまとめ
必要なフォームを作成する

フォームの onSubmit を使用して addHome 関数を呼び出す

addHome はフォームで入力された data を引数として持つ
axios によって data 情報を持った POST リクエストが作成される

api > homes.jshandler 内で POST リクエストが処理されレスポンスが作成される.
こいつが肝っぽい。

  • req.bodyからPOSTの値を取得.
  • await prisma.home.create()を使用してDBに登録.
  • res.status(200).json(home) で新しく作成された home レコードを返す

わかってないことろ

  • res.status(200).json(home) は新しく追加されたデータを返すが、今までのデータはどこで取得しているのか。

    解決
    index.js で最初に取得していた。

  • index.js から呼び出されるコンポーネントの実装方法

    • index.js > Grid.js > Card.js 等となっててわかりにくい
    • 後で確認する

静的生成(SSG)を使用したNext.jsのデータのクエリ

Next.jsでのページの事前レンダリング

SSG の説明
必要なHTMLがビルド時に生成されるらしい。

データを使用した静的生成

getStaticProps()を使用する。
SSR と似ててデータ取得の方法はこんな感じ。

// データ取得.
export async function getStaticProps() {
  const data = ...
  return {
    props: //...
  }
}
// 実際にレンダリング
export default function MyPage(props) { ... }

動的ルートを作成する

pages の下に[xxxx].js を配置するやつ。
表示部分はは長いので省略.

[id].js
export async function getStaticPaths() {
  const prisma = new PrismaClient();
  const homes = await prisma.home.findMany({
    // id から各レコードのフィールドを取得する.
    select: { id: true },
  });
    return {
    paths: homes.map(home => ({
      params: { id: home.id },
    })),
    fallback: false,
  };
}
getStaticPaths() と getStaticProps()

https://nextjs.org/docs/basic-features/data-fetching/get-static-paths

  • getStaticPaths
    • ページ生成用。のイメージ。
    • ビルド時に実行される。
    • 事前にパスを設定しておく。
    • falback はないときの対応。
    • ページに動的ルートと getStaticProps が使用されている場合、パスのリストを定義する必要がある。
    • 指定しておくことでパスを事前に生成できる。
    • getStaticPathsgetStaticProps は一緒に使うもの。
  • getStaticProps
    • getStaticPropsはデータフェッチ(外部データ等の取得)に使う。
      • const res = await fetch('https://.../posts') 等
    • そして props としてページコンポーネントにデータを渡す
    • 動的ルート(pages 以下)でのみ使用できる。
      公式の例
export async function getStaticPaths() {
  return {
    paths: [
      { params: { ... } }
    ],
    fallback: true // false or 'blocking'
  };
}
export async function getStaticProps({ params }) {
  // params.id (現在のページのID) から データを取得.
  const home = await prisma.home.findUnique({
    where: { id: params.id },
  });
  // 現在のページのデータがあれば.
  if (home) {
    return {
      // パースしたデータを取得.
      props: JSON.parse(JSON.stringify(home)),
    };
  }
  // これはなにやってるんだ.
  return {
    redirect: {
      destination: '/',
      permanent: false,
    },
  };
}

めっちゃ遅いとおもったら開発では毎回 getStaticProps が実行されてるらしい。

インクリメンタル静的生成を有効にする

この教材ではなぜか使われてない。
getStaticPaths で fallback: true を設定することでリクエストページのフォールバックバージョンが提供される。自動的に getStaticProps が呼び出されてデータが取得される。

そしてめっちゃはやくなった。

SupabaseStorageからファイルを保存して提供する

ファイルアップロード アプリからファイルにアクセス

Storage から設定を行う.
外部ファイルを読み取るには next.config.js に設定が必要

module.exports = {
  images: {
    domains: ['ホスト名'],
  }
}

セキュリティルールを追加

CRUDのルールを特定のバケットに適応できる

アプリからファイルをアップロード

APIエンドポイントの作成
export default async function handler(req, res) {}の出番
SupabaseJS クライアントが必要
npm i @supabase/supabase-js

カスタムAPI構成

APIルートで config をエクスポートすることで独自の設定ができる

export const config = {
  api: {
    bodyParser: {
      sizeLimit: '10mb',
    },
  },
};

Discussion

ログインするとコメントできます