🐷

Next.js x Supabase x TypeScript で公式クイックスタートを実装

2023/02/10に公開
3

はじめに

  • 目的は、Supabase の公式クイックスタートに記載されていることだけを TypeScript で実装することです。
  • Supabase とは何かを簡単に紹介します。

ソースは以下を参照ください。

https://github.com/hayato94087/nextjs-supabase-quickstart

Supabaseとは

Supabase とは何かです。

  • Firebase の代替品とされているソリューションです。
  • 主な機能としてデータベース、認証機能、ストレージ、エッジ機能があります。
  • Supabase を利用することで、Firebase のような機能を自分の環境に構築できます。

以下が公式サイトです。

https://supabase.io

英語ですが Supabase の紹介が 2 分半で簡潔にされています。

https://www.youtube.com/watch?v=zBZgdTb-dns

Supabaseの特徴

Supabase の特徴を 3 点あげます。

特徴1:データベースがPostgreSQL

  • SQL の知識があれば、データベースの操作がすぐそうできるため、学習コストが低いです。
  • RDB を利用しているため、データベースの設計が容易です。

特徴2:オープンソースあるいはホスティングサービスを利用できる

  • オープンソースで提供されているため、自身の環境に構築できます。
  • 自身の環境で構築したくない場合は、Supabase のホスティングを利用し手軽に利用を開始することが出来ます。

特徴3:認証機能がある

  • メールアドレスとパスワードで認証できます。
  • マジックリンクを利用した認証もできます。
  • Social Login を利用した認証もできます。
  • SMS 認証も可能です。

Supabaseに興味を持った背景

個人的に Supabase に興味を持った背景は、以下です。

理由1:サービスを立ち上げる際に、データベースの設計に悩む時間を減らせる

  • サービス立ち上げ初期ははビジネス要件が変動するため、データベースの変更が必要です。
  • お客様への提供価値に直結する開発へ時間を使ったほうが良いため、データベース設計に悩む時間は短いほうが良いです。例えば DynamoDB にデータを格納する場合は、常にどうやってデータを関係性をもたせて抽出するか、常に悩む必要があります。
  • データベースが拡張でき、整合性をデータを持ち引き出せること、は魅力的に感じました。

理由2:サービスの成長にあわせてインフラの変更できる

  • 次に、良いと思ったのは、特徴2:オープンソースあるいはホスティングサービスを利用できることです。
  • サービス立ち上げ初期はホスティングサービスを利用し、迅速にサービスリリースを行うことができます。インフラを心配する必要はありません。
  • サービスが成長していくにつれて自身のインフラに移行する選択ができます。例えば AWS の EC2 に移行し、ビジネスニーズにあわせてインフラの設計が可能です。

Supabaseでプロジェクトを作成

以下に沿って Supabase のプロジェクトを作成していきます。TypeScript で動作させるためにところどころコードは修正します。

https://supabase.com/docs/guides/getting-started/quickstarts/nextjs

サイトにアクセスします。

https://supabase.io

Start your project をクリックします。

Continue with GitHub をクリックします。

GitHub のアカウントにログインします。

Authorize supabase をクリックします。

New Project をクリックします。

  • Name にプロジェクト名を入力します。
  • Database Password にパスワードを入力します。
  • Region は日本を選択します。
  • Pricing Plan は Free を選択します。
  • Create new project をクリックします。

プロジェクトが作成完了しました。

Supabaseにテーブルを作成

  • Supabase にサンプルデータを含むテーブルを作成します。
  • New query をクリックします。

以下の SQL を入力します。

SQL
 -- Create the table
 CREATE TABLE countries (
 id SERIAL PRIMARY KEY,
 name VARCHAR(255) NOT NULL
 );
 -- Insert some sample data into the table
 INSERT INTO countries (name) VALUES ('United States');
 INSERT INTO countries (name) VALUES ('Canada');
 INSERT INTO countries (name) VALUES ('Mexico');

RUN をクリックし、テーブルを作成します。

Sucess. No rows returned.を確認できたら OK です。

Table のアイコンをクリックします。

  • countries テーブルが作成されています。
  • countries をクリックします。

3 件のデータが登録されていることが確認できます。

Next.jsのプロジェクトを作成

Next.js のプロジェクトを作成します。

terminal
$ yarn create next-app nextjs-supabase --typescript --eslint --src-dir --import-alias "@/*"

実行結果

terminal
yarn create v1.22.19
warning package.json: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-next-app@13.1.6" with binaries:
      - create-next-app
✔ Would you like to use experimental `app/` directory with this project? … No / Yes
Creating a new Next.js app in /Users/hayato94087/Work/nextjs-supabase.

Using yarn.

Installing dependencies:
- react
- react-dom
- next
- @next/font
- typescript
- @types/react
- @types/node
- @types/react-dom
- eslint
- eslint-config-next

yarn add v1.22.19
warning ../package.json: No license field
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 196 new dependencies.
info Direct dependencies
├─ @next/font@13.1.6
├─ @types/node@18.13.0
├─ @types/react-dom@18.0.10
├─ @types/react@18.0.27
├─ eslint-config-next@13.1.6
├─ eslint@8.33.0
├─ next@13.1.6
├─ react-dom@18.2.0
├─ react@18.2.0
└─ typescript@4.9.5
info All dependencies
├─ @babel/runtime@7.20.13
├─ @eslint/eslintrc@1.4.1
├─ @humanwhocodes/config-array@0.11.8
├─ @humanwhocodes/module-importer@1.0.1
├─ @humanwhocodes/object-schema@1.2.1
├─ @next/env@13.1.6
├─ @next/eslint-plugin-next@13.1.6
├─ @next/font@13.1.6
├─ @next/swc-darwin-x64@13.1.6
├─ @nodelib/fs.scandir@2.1.5
├─ @nodelib/fs.stat@2.0.5
├─ @nodelib/fs.walk@1.2.8
├─ @pkgr/utils@2.3.1
├─ @rushstack/eslint-patch@1.2.0
├─ @swc/helpers@0.4.14
├─ @types/json5@0.0.29
├─ @types/node@18.13.0
├─ @types/prop-types@15.7.5
├─ @types/react-dom@18.0.10
├─ @types/react@18.0.27
├─ @types/scheduler@0.16.2
├─ @typescript-eslint/parser@5.51.0
├─ @typescript-eslint/scope-manager@5.51.0
├─ @typescript-eslint/typescript-estree@5.51.0
├─ acorn-jsx@5.3.2
├─ acorn@8.8.2
├─ ajv@6.12.6
├─ ansi-regex@5.0.1
├─ ansi-styles@4.3.0
├─ argparse@2.0.1
├─ aria-query@5.1.3
├─ array-includes@3.1.6
├─ array-union@2.1.0
├─ array.prototype.flat@1.3.1
├─ array.prototype.tosorted@1.1.1
├─ ast-types-flow@0.0.7
├─ axe-core@4.6.3
├─ axobject-query@3.1.1
├─ balanced-match@1.0.2
├─ brace-expansion@1.1.11
├─ braces@3.0.2
├─ callsites@3.1.0
├─ caniuse-lite@1.0.30001451
├─ chalk@4.1.2
├─ client-only@0.0.1
├─ color-convert@2.0.1
├─ color-name@1.1.4
├─ concat-map@0.0.1
├─ cross-spawn@7.0.3
├─ csstype@3.1.1
├─ damerau-levenshtein@1.0.8
├─ deep-is@0.1.4
├─ define-lazy-prop@2.0.0
├─ emoji-regex@9.2.2
├─ enhanced-resolve@5.12.0
├─ es-get-iterator@1.1.3
├─ es-set-tostringtag@2.0.1
├─ es-to-primitive@1.2.1
├─ escape-string-regexp@4.0.0
├─ eslint-config-next@13.1.6
├─ eslint-import-resolver-node@0.3.7
├─ eslint-import-resolver-typescript@3.5.3
├─ eslint-module-utils@2.7.4
├─ eslint-plugin-import@2.27.5
├─ eslint-plugin-jsx-a11y@6.7.1
├─ eslint-plugin-react-hooks@4.6.0
├─ eslint-plugin-react@7.32.2
├─ eslint-scope@7.1.1
├─ eslint-utils@3.0.0
├─ eslint@8.33.0
├─ esquery@1.4.0
├─ esrecurse@4.3.0
├─ estraverse@5.3.0
├─ fast-deep-equal@3.1.3
├─ fast-glob@3.2.12
├─ fast-json-stable-stringify@2.1.0
├─ fast-levenshtein@2.0.6
├─ fastq@1.15.0
├─ file-entry-cache@6.0.1
├─ fill-range@7.0.1
├─ find-up@5.0.0
├─ flat-cache@3.0.4
├─ flatted@3.2.7
├─ function.prototype.name@1.1.5
├─ get-symbol-description@1.0.0
├─ get-tsconfig@4.4.0
├─ glob-parent@6.0.2
├─ glob@7.1.7
├─ globalthis@1.0.3
├─ globalyzer@0.1.0
├─ globby@13.1.3
├─ globrex@0.1.2
├─ graceful-fs@4.2.10
├─ grapheme-splitter@1.0.4
├─ has-bigints@1.0.2
├─ has-flag@4.0.0
├─ has-proto@1.0.1
├─ import-fresh@3.3.0
├─ imurmurhash@0.1.4
├─ is-bigint@1.0.4
├─ is-boolean-object@1.1.2
├─ is-callable@1.2.7
├─ is-date-object@1.0.5
├─ is-docker@2.2.1
├─ is-extglob@2.1.1
├─ is-map@2.0.2
├─ is-negative-zero@2.0.2
├─ is-number-object@1.0.7
├─ is-number@7.0.0
├─ is-path-inside@3.0.3
├─ is-set@2.0.2
├─ is-string@1.0.7
├─ is-symbol@1.0.4
├─ is-weakmap@2.0.1
├─ is-weakref@1.0.2
├─ is-weakset@2.0.2
├─ is-wsl@2.2.0
├─ isexe@2.0.0
├─ js-sdsl@4.3.0
├─ js-tokens@4.0.0
├─ json-schema-traverse@0.4.1
├─ json-stable-stringify-without-jsonify@1.0.1
├─ json5@1.0.2
├─ jsx-ast-utils@3.3.3
├─ language-subtag-registry@0.3.22
├─ language-tags@1.0.5
├─ locate-path@6.0.0
├─ lodash.merge@4.6.2
├─ loose-envify@1.4.0
├─ lru-cache@6.0.0
├─ micromatch@4.0.5
├─ minimatch@3.1.2
├─ minimist@1.2.7
├─ ms@2.1.2
├─ nanoid@3.3.4
├─ natural-compare@1.4.0
├─ next@13.1.6
├─ object-assign@4.1.1
├─ object-inspect@1.12.3
├─ object-is@1.1.5
├─ object.hasown@1.1.2
├─ open@8.4.1
├─ optionator@0.9.1
├─ p-limit@3.1.0
├─ p-locate@5.0.0
├─ parent-module@1.0.1
├─ path-exists@4.0.0
├─ path-key@3.1.1
├─ path-type@4.0.0
├─ picomatch@2.3.1
├─ postcss@8.4.14
├─ prop-types@15.8.1
├─ punycode@2.3.0
├─ queue-microtask@1.2.3
├─ react-dom@18.2.0
├─ react-is@16.13.1
├─ react@18.2.0
├─ regenerator-runtime@0.13.11
├─ regexpp@3.2.0
├─ resolve-from@4.0.0
├─ reusify@1.0.4
├─ rimraf@3.0.2
├─ run-parallel@1.2.0
├─ safe-regex-test@1.0.0
├─ scheduler@0.23.0
├─ shebang-command@2.0.0
├─ shebang-regex@3.0.0
├─ slash@4.0.0
├─ source-map-js@1.0.2
├─ stop-iteration-iterator@1.0.0
├─ string.prototype.matchall@4.0.8
├─ string.prototype.trimend@1.0.6
├─ string.prototype.trimstart@1.0.6
├─ strip-ansi@6.0.1
├─ strip-bom@3.0.0
├─ strip-json-comments@3.1.1
├─ styled-jsx@5.1.1
├─ supports-color@7.2.0
├─ synckit@0.8.5
├─ tapable@2.2.1
├─ text-table@0.2.0
├─ tiny-glob@0.2.9
├─ to-regex-range@5.0.1
├─ tsconfig-paths@3.14.1
├─ tsutils@3.21.0
├─ type-check@0.4.0
├─ type-fest@0.20.2
├─ typed-array-length@1.0.4
├─ typescript@4.9.5
├─ unbox-primitive@1.0.2
├─ uri-js@4.4.1
├─ which-collection@1.0.1
├─ which@2.0.2
├─ word-wrap@1.2.3
├─ yallist@4.0.0
└─ yocto-queue@0.1.0
✨  Done in 7.76s.

Initializing project with template: default

Initialized a git repository.

Success! Created nextjs-supabase at /Users/hayato94087/Work/nextjs-supabase

✨  Done in 15.01s.

Supabase client libraryをインストール

  • Supabase を操作するために、supabase-js をインストールします。
  • supabase-js は、Supabase の API を呼び出すためのライブラリです。
terminal
$ cd nextjs-supabase
$ yarn add @supabase/supabase-js 

実行結果

terminal
yarn add v1.22.19
warning ../package.json: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 23 new dependencies.
info Direct dependencies
└─ @supabase/supabase-js@2.7.1
info All dependencies
├─ @supabase/functions-js@2.0.0
├─ @supabase/gotrue-js@2.11.0
├─ @supabase/postgrest-js@1.4.0
├─ @supabase/realtime-js@2.6.0
├─ @supabase/storage-js@2.3.0
├─ @supabase/supabase-js@2.7.1
├─ @types/phoenix@1.5.4
├─ bufferutil@4.0.7
├─ d@1.0.1
├─ es6-iterator@2.0.3
├─ es6-symbol@3.1.3
├─ ext@1.7.0
├─ is-typedarray@1.0.0
├─ next-tick@1.1.0
├─ node-fetch@2.6.7
├─ tr46@0.0.3
├─ type@1.2.0
├─ typedarray-to-buffer@3.1.5
├─ utf-8-validate@5.0.10
├─ webidl-conversions@3.0.1
├─ websocket@1.0.34
├─ whatwg-url@5.0.0
└─ yaeti@0.0.6
✨  Done in 6.71s.

Supabase clientを作成

Supabase client を src/lib/supabaseClient.ts に作成します。

src/lib/supabaseClient.ts
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL as string;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

Supabase clientを使用しデータを取得

  • src/pages/page.tsx を作成します。
  • Supabase client を使用して、データを取得する処理を追加します。
  • テーブルの型を定義します。
src/pages/page.tsx
import { supabase } from 'src/lib/supabaseClient';

type Country = {
  id: number;
  name: string;
};

type Props = {
  countries: Country[];
};

const Page = ({ countries }: Props) => {
  return (
    <ul>
      {countries.map((country) => (
        <li key={country.id}>{country.name}</li>
      ))}
    </ul>
  );
};

export const getServerSideProps = async () => {
  let { data } = await supabase.from('countries').select();
  return {
    props: {
      countries: data,
    },
  };
};

export default Page;

環境ファイルを設定

  • Supabase の環境変数を設定します.
  • Supabase の管理画面から Project URLProject API Key(anon) を取得します。

.env.local に、値を設定します。

.env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxxxxxxxxxxxxxxxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

動作を確認

ローカルで動作を確認します。

terminal
$ yarn dev

Supabaseのプロジェクトを削除

以下の手順で、Supabase のプロジェクトを削除します。

プロジェクトを選択します。

設定ボタンをクリックします。

下にスクロールします。

「Delete Project」をクリックします。

  • プロジェクト名を入力します。
  • 「I understand, delete this project」をクリックします。

プロジェクト削除が完了しました。

まとめ

Supabase を使用して、Next.js でデータを取得する方法を紹介しました。

参考

Discussion

MSKMSK

初めましてこんばんは!
すみませんご質問なのですが、
API KEY(anon)にNEXT_PUBLIC_プレフィックスをつけても問題ないんでしょうか??

hayato94087hayato94087

はじめまして!

以下の通りしているのは、

.env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxxxxxxxxxxxxxxxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

下記で、環境変数の変数名をNEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYとしているからです!

src/lib/supabaseClient.ts
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL as string;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

以下の通り、.env.localとsupabaseClient.tsの両方からNEXT_PUBLIC_を削っても動きます!

.env.local
SUPABASE_URL=https://xxxxxxxxxxxxxxxxxxxxxxxx.supabase.co
SUPABASE_ANON_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
src/lib/supabaseClient.ts
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = process.env.SUPABASE_URL as string;
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY as string;

export const supabase = createClient(supabaseUrl, supabaseAnonKey);
MSKMSK

初心者なので、深い所までは熟知してないのですが
開発環境なのでフロントから参照出来るNEXT_PUBLIC_プレフィックスを当てたと言う事だったのですね。

丁寧にご回答頂きありがとうございました!