LoginSignup
24
15

[サンプルアプリ ハンズオン ] Next.js+Supabaseで簡単な認証とDB操作アプリを作る

Posted at

はじめに

簡単なアプリを作りながらSupabaseについて理解を深める。

想定読者
・Supabase初心者でSupabaseについて知りたい人
・Supabaseを使ってお試しで簡単なアプリを作ってみたい人

Next.jsの解説は含まれていません。
また、公式ドキュメントに記載があるサンプルアプリを動かしてみたという記事のため、
Supabaseを使用してバリバリコードを書いている方は対象読者から外れます。

技術スタック

  • Next.js 13
  • Supabase

公式HP

サンプルアプリの解説

サンプルアプリでは、メール認証を行いログインをし、
ログイン後、DBに保存されたプロフィールデータを表示する。
簡単な機能だが、認証とDBからデータの取得や更新などができるので
このアプリを改造すればいろんなWebアプリが作れるようになる。

サンプルアプリの動作手順は以下の通り。

①TOP画面でメールアドレスを入力する
image.png

②ログインメールが届く

③Log inをクリックでユーザページが開き、DBのデータ(プロフィール情報)が表示される
image.png

SupabaseのDB管理画面を確認すると、ブラウザに表示されていたデータがテーブルに格納されている。
そのため、ログアウトして再度ログインしても同じ情報を表示することができる。
また、ブラウザ画面で情報を変更して「Update」ボタンを押すとデータがちゃんと更新される。
image.png

Supabaseとは

SupabaseはBaaS(Backend as a Service) の一種でバックエンド(アプリの裏側)の機能を提供してくれるサービスです。
BaaSを使うとバックエンドで必要な機能を開発することなく利用することができるので、アプリ開発が早くなるというメリットがあります。

Supabaseは以下の機能を提供しています。
1.Database (データベース)
2.Auth (認証)
3.Storage (データ保存(画像など))
4.AI&Vector (AI)
5.Realtime
6.Edge Function

今回のサンプルアプリは1と2を使います。4~6は具体的にどんな機能なのかよく分かっていません。。

サンプルアプリのハンズオン

ここからサンプルアプリを作ります。
参考にしたサイトは以下になります。

プロジェクトを作成する

まずはSupabaseでプロジェクトを作成します。
公式HPにアクセスして「Start your project」をクリック。

アカウントを作成していない人はまず、アカウント作成から始まります。
私はGitHubアカウントを持っているので「Continue with GitHub」をクリック。

ボタンをポチポチしていくとアカウントが作成されます。

アカウントが作成されるとダッシュボード画面に遷移します。
そこで「New Project」をクリック

プロジェクト作成画面にいくので
・Name
・Database Password
・Region
・Pricing Plan
を入力して右下の「Create New Project」をクリック。

そしたらプロジェクトが作成されて「Welcome to your new project」ページに遷移します。

データベースを用意する

ダッシュボード画面で左にあるアイコンから「SQL Editer」を選択し、
「Uer Management Starter」をクリックします。

profilesテーブルを作成するSQL文が最初から用意されているので何も変更せずに
右下の「RUN」をクリックします。

API Keyを確認する

ホーム画面に戻り、「View API settings」をクリックします。

API Settingsが確認できます。この画面のURLとanon Keyは後でプログラムに書き込みます。

新規プロジェクトを作成する(Next.jsの準備)

VS Codeを開いてプロジェクトを作成したいフォルダに移動します。
私はドキュメント直下に「Supabase」というフォルダを作成し、その中にプロジェクトを作成することにしました。

そのため、VS Codeで「Supabase」フォルダを開いた状態でVSCodeのターミナルに以下のコマンドを入力します。末尾の「supabase-nextjs」は作成するプロジェクトの名前のため好きな名前を設定して良いですが、私は公式ドキュメントの例をそのまま使いました。

npx create-next-app@latest --use-npm supabase-nextjs

コマンドを入力してエンターを押すと以下のように何問か質問を聞かれます。
(例えば言語を「javaScript」にするか「TypeScript」にするかなど。)
ここはデフォルトの設定のままエンター連打で問題ないですが、私は「TypeScript」を選択してみました。

Nextjsアプリの構築方法や質問も意味などを知りたい方は、この方の記事が参考になると思います。

少し待つとプロジェクトが自動で作成され、「Success!」と表示され、準備ができます。

プロジェクトが作成されたら以下のコマンドでプロジェクトの中に移動する。

cd supabase-nextjs

次に「supabase-js」というライブラリをインストールするために以下のコマンドを実行します。

npm install @supabase/supabase-js

次に「.env.local」ファイルをsupabase-nextjsフォルダ直下に新規で作成します。
その「.env.local」ファイル内で
・NEXT_PUBLIC_SUPABASE_URL
・NEXT_PUBLIC_SUPABASE_ANON_KEY
を設定します。
ここに先ほど確認したURLとAnon Keyを以下のように入力します。

NEXT_PUBLIC_SUPABASE_URL=先ほど確認したURL
NEXT_PUBLIC_SUPABASE_ANON_KEY=先ほど確認したanon Key

こんな感じです。(URLとanonKeyは隠しています。)
image.png

次に以下のコマンドを入力してauth-helpers-nextjsライブラリをインストールします。

npm install @supabase/auth-helpers-nextjs

middlewareを設定する

middlewareとはリダイレクトをしたり認証をできるようにしてくれるライブラリ?です。
とりあえず認証がしたいときは必要そうです。(自信なくてすみません。)

公式HPは以下になります。

また、この方の記事が分かりやすかったです。

supabase-nextjsフォルダ直下に「middleware.ts」ファイルを新規作成し、
以下のコードをコピーします。

import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'

import type { NextRequest } from 'next/server'

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })

  const {
    data: { user },
  } = await supabase.auth.getUser()

  // if user is signed in and the current path is / redirect the user to /account
  if (user && req.nextUrl.pathname === '/') {
    return NextResponse.redirect(new URL('/account', req.url))
  }

  // if user is not signed in and the current path is not / redirect the user to /
  if (!user && req.nextUrl.pathname !== '/') {
    return NextResponse.redirect(new URL('/', req.url))
  }

  return res
}

export const config = {
  matcher: ['/', '/account'],
}

次に、auth-ui-reactライブラリとauth-ui-sharedをインストールするために、ターミナルで以下のコマンドを入力します。

npm install @supabase/auth-ui-react @supabase/auth-ui-shared

ここからは認証機能を作るためにたくさんファイルを作成します。
全部公式ホームページからコピペですが以下の通りです。

supabase-nextjsフォルダ直下にappフォルダを新規作成
appフォルダの直下に「auth-form.tsx」を作成。コードは以下の通り。

'use client'
import { Auth } from '@supabase/auth-ui-react'
import { ThemeSupa } from '@supabase/auth-ui-shared'
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
import { Database } from './database.types'

export default function AuthForm() {
  const supabase = createClientComponentClient<Database>()

  return (
    <Auth
      supabaseClient={supabase}
      view="magic_link"
      appearance={{ theme: ThemeSupa }}
      theme="dark"
      showLinks={false}
      providers={[]}
      redirectTo="http://localhost:3000/auth/callback"
    />
  )
}

appフォルダ直下に「page.js」ファイルを新規作成。コードは以下の通り

import AuthForm from './auth-form'

export default function Home() {
  return (
    <div className="row">
      <div className="col-6">
        <h1 className="header">Supabase Auth + Storage</h1>
        <p className="">
          Experience our Auth and Storage through a simple profile management example. Create a user
          profile and upload an avatar image. Fast, simple, secure.
        </p>
      </div>
      <div className="col-6 auth-widget">
        <AuthForm />
      </div>
    </div>
  )
}

appフォルダ直下にauthフォルダを新規作成。
authフォルダ直下にcallbackフォルダを新規作成。
callbackフォルダ直下にroute.tsファイルを新規作成。コードは以下の通り。

import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'

export async function GET(req: NextRequest) {
  const supabase = createRouteHandlerClient({ cookies })
  const { searchParams } = new URL(req.url)
  const code = searchParams.get('code')

  if (code) {
    await supabase.auth.exchangeCodeForSession(code)
  }

  return NextResponse.redirect(new URL('/account', req.url))
}

appフォルダ直下のauthフォルダ直下にsignoutフォルダを新規作成。
signoutフォルダ直下にroute.jsファイルを新規作成。コードは以下の通り。

import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export async function POST(req) {
  const supabase = createRouteHandlerClient({ cookies })

  // Check if we have a session
  const {
    data: { session },
  } = await supabase.auth.getSession()

  if (session) {
    await supabase.auth.signOut()
  }

  return NextResponse.redirect(new URL('/', req.url), {
    status: 302,
  })
}

appフォルダ直下にaccountフォルダを新規作成。
accountフォルダ直下にaccount-form.tsxファイルを新規作成。コードは以下の通り

'use client'
import { useCallback, useEffect, useState } from 'react'
import { Database } from '../database.types'
import { Session, createClientComponentClient } from '@supabase/auth-helpers-nextjs'

export default function AccountForm({ session }: { session: Session | null }) {
  const supabase = createClientComponentClient<Database>()
  const [loading, setLoading] = useState(true)
  const [fullname, setFullname] = useState<string | null>(null)
  const [username, setUsername] = useState<string | null>(null)
  const [website, setWebsite] = useState<string | null>(null)
  const [avatar_url, setAvatarUrl] = useState<string | null>(null)
  const user = session?.user

  const getProfile = useCallback(async () => {
    try {
      setLoading(true)

      let { data, error, status } = await supabase
        .from('profiles')
        .select(`full_name, username, website, avatar_url`)
        .eq('id', user?.id)
        .single()

      if (error && status !== 406) {
        throw error
      }

      if (data) {
        setFullname(data.full_name)
        setUsername(data.username)
        setWebsite(data.website)
        setAvatarUrl(data.avatar_url)
      }
    } catch (error) {
      alert('Error loading user data!')
    } finally {
      setLoading(false)
    }
  }, [user, supabase])

  useEffect(() => {
    getProfile()
  }, [user, getProfile])

  async function updateProfile({
    username,
    website,
    avatar_url,
  }: {
    username: string | null
    fullname: string | null
    website: string | null
    avatar_url: string | null
  }) {
    try {
      setLoading(true)

      let { error } = await supabase.from('profiles').upsert({
        id: user?.id as string,
        full_name: fullname,
        username,
        website,
        avatar_url,
        updated_at: new Date().toISOString(),
      })
      if (error) throw error
      alert('Profile updated!')
    } catch (error) {
      alert('Error updating the data!')
    } finally {
      setLoading(false)
    }
  }

  return (
    <div className="form-widget">
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" type="text" value={session?.user.email} disabled />
      </div>
      <div>
        <label htmlFor="fullName">Full Name</label>
        <input
          id="fullName"
          type="text"
          value={fullname || ''}
          onChange={(e) => setFullname(e.target.value)}
        />
      </div>
      <div>
        <label htmlFor="username">Username</label>
        <input
          id="username"
          type="text"
          value={username || ''}
          onChange={(e) => setUsername(e.target.value)}
        />
      </div>
      <div>
        <label htmlFor="website">Website</label>
        <input
          id="website"
          type="url"
          value={website || ''}
          onChange={(e) => setWebsite(e.target.value)}
        />
      </div>

      <div>
        <button
          className="button primary block"
          onClick={() => updateProfile({ fullname, username, website, avatar_url })}
          disabled={loading}
        >
          {loading ? 'Loading ...' : 'Update'}
        </button>
      </div>

      <div>
        <form action="/auth/signout" method="post">
          <button className="button block" type="submit">
            Sign out
          </button>
        </form>
      </div>
    </div>
  )
}

appフォルダ直下のaccountフォルダ直下にpage.tsxファイルを新規作成。コードは以下の通り。

import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { Database } from '../database.types'
import AccountForm from './account-form'

export default async function Account() {
  const supabase = createServerComponentClient<Database>({ cookies })

  const {
    data: { session },
  } = await supabase.auth.getSession()

  return <AccountForm session={session} />
}

これでサンプルアプリは完成!
ターミナルで以下のコマンドを実行するとアプリが起動する

npm run dev

ちなみにここまでの手順は上記で紹介した公式HPに全部書いてあるが
私は記載通りにここまで試したが、エラーが出てしまった。
具体期には以下のエラーが出た。

Parsing error: Cannot find module 'next/babel'

ちょっと分からなくでChatGPTちゃんに聞いたところ、バージョンの互換性の問題のようで、
以下の指示通りに実行したらエラーが消えてくれた。
もし同様のエラーが出た際は実施をしてみてほしい。

image.png

まとめ

SupabaseはBaaSの一種で認証やDBなどの機能を提供してくれる。
Next.jsやVueなどさまざまなフレームワークに対応している。
使うためにはSupabaseのアカウント作成、プロジェクト作成が必要だが簡単にできる。
ソースコードにライブラリを入れるときもコマンドを入力すれば簡単にインストールできる。

最後に

簡単にバックエンド側の処理が実装できて、確かに開発効率が上がると感じた。
サンプルコードを動かせたのはいいが、理解を深めるためにアプリを自作したい!

参考資料

私がこの記事を書くために参考にした資料を紹介します。
私の記事は読まなくても、以下の資料を確認すればきっとめちゃくちゃ理解できます!

プログラミングチュートリアルさんのyoutube動画

この方の動画はどれも分かりやすいです。Udemy講座も多く公開していますが、どれも分かりやすいです。

Qiita記事

create-next-appを用いたNextjsアプリ構築方法

create-next-appを用いたNextjsアプリ構築方法が丁寧に書いています。

飛躍的に伸びているBaaS「Supabase」の概要と所感

Supabaseについて分かりやすくまとめられています。

24
15
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
15