38
24

More than 1 year has passed since last update.

話題のSupabaseでサクッと全文検索機能をつくってみた!

Last updated at Posted at 2023-02-19

はじめに

こんにちは!かほです♪🐥
現在、本業では技術広報業務やエンジニア業務などに従事しています。
今回の記事では、Supabase×Next.jsを用いたアプリ開発における全文検索機能の実装方法について説明します。

※全文検索・・・一応念のため、今回のタイトルにある「全文検索」の意味はRDBMS側の用語から来ています。下記で行なっている内容としては、SQLのLIKE句で%keyword%していることと変わらないですが、LIKE句の場合はインデックスが効かない問題があるということです。RDBMSのFull Text Searchを使うことで、事前にインデックスを貼って活かすことができます。今回の「全文検索」というキーワードはRDBMSにおける「全文検索」です。

下記の3記事の内容はすでに実装済です🙇‍♀️
まだ未実装の方は、ご参考までに♪🐥

この記事の読者対象

Supabase×Next.jsで全文検索機能を含んだアプリを今後作ってみたい方
Next.jsを使ったリアルタイム検索機能の実装方法を知りたい方
Supabaseを今後使ってみたい方
Next.jsを使って高速でアプリを作りたい方

開発環境

package.json
 "dependencies": {
    "@supabase/supabase-js": "^2.2.1",
    "eslint": "8.28.0",
    "eslint-config-next": "13.0.6",
    "next": "13.0.6",
    "react": "18.2.0",
    "react-dom": "18.2.0",
  },
  "devDependencies": {
    "@types/node": "^18.11.13",
    "@types/react": "18.0.26",
  }

リアルタイム検索機能を実装しよう

下記がsearch.tsxの全体像です。

search.tsx
import Head from "next/head";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import { useState, useEffect } from "react";

export default function Search() {
  const [posts, setPosts] = useState([]);
  const [keyword, setKeyword] = useState("");

  useEffect(() => {
    fetchPosts();
  }, []);

  async function fetchPosts() {
    const { data } = await supabase.from("posts").select("*");
    setPosts(data);
  }

  const search = async (value: string) => {
    if (value !== "") {
      const { data: posts, error } = await supabase
        .from("posts")
        .select()
        .textSearch("title", `${value}`);//どんな検索方法を行いたいかによって、書き方が変わります。
      if (error) throw error;
      setPosts(posts);
      return;
    }else{
      await fetchPosts();
    }
  };

  const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    setKeyword(e.target.value);
    search(e.target.value);
  };

  return (
    <>
      <div className={styles.container}>
        <Head>
          <title>検索機能</title>
          <link rel="icon" href="/favicon.ico" />
        </Head>
        <main className={styles.main}>
          <div>
            <h1>検索機能</h1>
            <div>
              <input
                type="text"
                value={keyword}
                className="my-8  rounded border border-black"
                placeholder="search"
                onChange={(e) => handleChange(e)}
              />
              <table>
                <thead>
                  <tr>
                    <th>投稿日</th>
                    <th>タイトル</th>
                    <th>内容</th>
                  </tr>
                </thead>
                <tbody>
                  {posts.map((post) => (
                    <tr key={post.id}>
                      <td>{post.created_at.substr(0, 10)}</td>
                      <td>{post.title}</td>
                      <td>{post.content}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </div>
        </main>
        <footer className={styles.footer}></footer>
      </div>
    </>
  );
}

コードを上から順に説明します。
fetchPosts()メソッドによって、投稿の一覧表示を行っています。
こちらのメソッドの説明については、下記の記事「話題のSupabaseでサクッと投稿機能をつくってみた!」の「一覧機能を実装しよう」の段落と内容が同じですので、ぜひご参考までに🙌

また、検索フォームに入れる値の状態管理を行っています。

search.tsx
  const [posts, setPosts] = useState([]);
  const [keyword, setKeyword] = useState("");

  useEffect(() => {
    fetchPosts();
  }, []);

  async function fetchPosts() {
    const { data } = await supabase.from("posts").select("*");
    setPosts(data);
  }

次にsupabase.tsを呼び出し、検索部分の実装を行います。
今回はtextSearch()を使用し、postsテーブルのデータを検索します。
from()の引数にテーブル名を入れ、textSearch()の第1引数にテーブルの対象カラム、第2引数に検索したいキーワード等を入れます。

textSearch()を使用する方法は、下記のsupabase公式ドキュメントにも記載されていますので、ご興味のある方はご参照ください🙌

今回は、if文で検索フォームに値がある場合とない場合のロジックを分けて書いています。
検索フォームに値がある場合は、上記の検索方法を用いて、データをsetPosts(posts)により状態管理します。
検索フォームに値がない場合は、投稿が一覧表示されるために、fetchPosts()メソッドを記述します。

search.tsx
 const search = async (value: string) => {
    if (value !== "") {
      const { data: posts, error } = await supabase
        .from("posts")
        .select()
        .textSearch("title", `${value}`);//どんな検索方法を行いたいかによって、書き方が変わります。
      if (error) throw error;
      setPosts(posts);
      return;
    }else{
      await fetchPosts();
    }
  };

最後に、検索フォームの値の状態管理を行います。
今回は検索フォームの値を入れたと同時に、検索結果の出力も行う想定です。
そのため、検索フォームの値とsearchメソッドによって出力される検索結果の値にonChangeイベントの設定を行います。

search.tsx
const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    setKeyword(e.target.value);
    search(e.target.value);
  };

その他の検索方法も試してみよう!

上記で説明した検索方法は、完全一致検索ですが、その他にも様々な検索方法を行うことができます。
そのうちの数個を軽く紹介します♪🐥

  • 完全一致検索
    上記で説明した検索方法です。完全に一致した値のみを検索します。
search.tsx
  const { data: posts, error } = await supabase
    .from("posts")
    .select()
    .textSearch("title", `${value}`)
  • OR検索
    OR(いずれかのキーワードを含む)検索は|でキーワードを連結します。
search.tsx
 const { data: posts, error } = await supabase
    .from("posts")
    .select()
    .textSearch("title", `'bbb' | 'ccc'`);
  • AND検索
    AND(すべてのキーワードを含む)検索はでキーワードを連結します。
search.tsx
   const { data: posts, error } = await supabase
    .from("posts")
    .select()
    .textSearch("title", `'bbb' & 'ccc'`);

追記

上記で説明した検索機能の実装方法の他にも、PGroongaと呼ばれる拡張機能(Extensions)を使った実装方法もあります!ご興味がある方は下記の公式ドキュメントをチェックしてみてください♪🐥

最後に

これで実装はおしまいです!

今回は、Supabase×Next.jsを用いたアプリ開発における全文検索機能の実装方法について説明しました。今後は主にフロントエンド周辺、コミュニティ運営、Tech PR(技術広報)の記事を中心に書いていく予定ですので、気になる方はぜひフォローをよろしくお願いします♪ではでは〜🐥

Twitterアカウント:https://twitter.com/kaho_eng

参考資料

38
24
0

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
38
24