はじめに
こんにちは!MYJLab Advent Calendar 2021の2日目を担当するサッチーです!
昨日の@chiba___さんの記事では、かわいいタートルを使って、アドベントカレンダー初日が盛り上がる記事を書いてくれました!⭐
わたしはアドベントカレンダーは3年連続の参加です!たのしんでいくぞ🔥
これは?
最近お友達との開発(あれ、研究...あれ、就活...)で、DBやAPIまわりの技術リサーチをしています。
その際に、最近自分の中で流行っている「GraphQL+Prisma」と、世間的に流行っている?「Supabase」を比較したいと思っていたので、今回はSupabaseを試してみました〜という記事をかきます!
次回の記事でGraphQLとPrismaのことを書く予定です。
Supabaseってなに?
Supabaseは、Firebaseの代替プラットフォームとして、登場しました。
詳しい説明は、こちらの記事で詳しくまとめられています!
今回はDBのみを試しますが、認証やストレージ機能も充実しています。
FirebaseとSupabaseの最もわかりやすい違いは、DBがPostgreSQLである点だと思います!
私はNo SQLが苦手なので、それだけでうれしいです。
やること
事前にProductsテーブルと、Tagsテーブルと、2つのテーブルの中間テーブルであるProductTagsテーブルを用意し、外部キーの設定までしておきます。(次回の記事でも使います)
ひとつの製品が複数のタグを持っていて、ひとつのタグは複数の製品につけられている多対多の状態です。
Supabaseでは、DBの作成〜外部キー設定などの基本的な操作は、GUIで簡単に行なえます。SQL Editorを使えばインデックスをはったり、RLCを設定したりなど、普通のPostgreSQLと同様に扱うことができます。
各テーブルのschemaは、以下のような感じです。
type Product = {
id: number
product_name: string
released_at: string
image_url: string
hp_url: string
average_price: number
tags: Tag[]
}
type Tag = {
id: number
tag_name: string
products: Product[]
}
type ProductTag = {
id: number
product_id: number
tag_id: number
}
このDBを使って、
- product一覧の取得
- tagごとのproduct一覧の取得
を試してみます!
今回私のユースケースでは、データの更新をたまにしかしないので、Next.jsでSSGをする例になっています。
SSR、CSR、SSGなどの説明は本記事では割愛しますが、こちらの記事にとてもわかりやすくまとめられています〜⭐
実装
supabaseの設定
公式ドキュメント通りにセットアップをします。
「NEXT_PUBLIC_SUPABASE_URL」と「NEXT_PUBLIC_SUPABASE_ANON_KEY」を探し出して、.env.local
に貼り付けます。
歯車→APIとたどると見つかると思います。
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
Product一覧
いちばんシンプルなProduct一覧からやってみます。
import { supabase } from '../utils/supabaseClient'
export async function getProducts(): Promise<Product[]> {
const { data, error, status } = await supabase
.from<Product>('products')
.select(`
*,
tags (
id,
tag_name
)
`)
if (error && status !== 406) {
throw error
}
return data;
}
個人的に最高だなと思った点は、
- selectの書き方がGraphQLライクでレスポンスの形が直感的にわかりやすい
- 中間テーブル作るだけで、ネストしたデータまで一回のリクエストで取得できる
です!
データを表示する際は以下のように使えると思います。
import { FC } from 'react'
import { getProducts } from '../../libs/product'
import ProductList from '../../components/templates/productList'
type Props = {
products: Product[];
};
const Products: FC<Props> = ({ products }) => {
return (
<div>
<ProductList products={products}/>
</div>
)
}
export async function getStaticProps() {
const products = await getProducts();
return {
props: {
products,
},
}
}
export default Products;
tagごとのproduct一覧
「いらすとや」というタグがついている商品のみを表示したい!みたいなケースです。
ルーティングはtag/[tagId]
みたいな感じにしようと思います。
Next.jsでは、getStaticPaths
というAPIを使うことで、SSGの際にも動的にルーティングを設定することができます。このAPIの中でpathを作るために、tagのID一覧を取得します。
import { supabase } from '../utils/supabaseClient'
export async function getTags(): Promise<Tag[]> {
const { data, error, status } = await supabase
.from<Tag>('tags')
.select(`
id
`)
if (error && status !== 406) {
throw error
}
return data;
}
次に、tagのIDを引数にとって、そのtagの名前と、tagがついているproduct(複数)を取得します。
export async function getTags(): Promise<Tag[]> {
// ・・・省略・・・
}
// ここから追加
export async function getTagProducts(tagId: number): Promise<Tag>{
const { data, error, status } = await supabase
.from<Tag>('tags')
.select(`
tag_name,
products (
*,
tags (
id,
tag_name
)
)
`)
.eq('id', tagId)
.single()
if (error && status !== 406) {
throw error
}
return data;
}
eq()
でidがtagIdのものだけを取得するようにし、single()
で返り値を配列ではなくて一つの要素にするようにしています。
取得したデータの表示
import { FC } from 'react'
import { getTags, getTagProducts } from '../../libs/tag'
import ProductList from '../../components/templates/productList'
type Props = {
tagName: string,
products: Product[];
};
const TagProducts: FC<Props> = ({ tagName, products }) => {
return (
<div>
<h1>{tagName}</h1>
<ProductList products={products}/>
</div>
)
}
export const getStaticPaths = async () => {
const tags = await getTags();
const paths = tags.map((tag:Tag) => `/tags/${tag.id}`);
return { paths, fallback: false };
};
export const getStaticProps = async (context) => {
const tagId = context.params.tagId;
const tagProducts = await getTagProducts(tagId);
return {
props: {
tagName: tagProducts.tag_name,
products: tagProducts.products,
},
};
};
export default TagProducts;
まとめ
- 導入が簡単
- PostgreSQL
- GraphQLっぽく、返してほしいカラムを指定できる
- TypeScriptと相性いい
など、趣味の開発では十分な機能が備わっているように感じました!
これから積極的に使っていきたいです。
気になる点や修正点などあればお申し付けください!フロントエンドまだまだ初心者なのでお手柔らかに🙏
最後までお読みいただきありがとうございました!