1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NextJSサーバーアクションにおけるSupabase共通関数を用いたクエリ生成の効率化

Posted at

はじめに

今回はNextJSのサーバーアクションを実装する際に、
Supabaseのクエリ生成を共通関数内に実装したことで、
より好ましいコードとなったことを共有したいため記事にしました!

時は深夜、NextJSでユーザーを挿入するサーバーアクションを作成していたところ、
supabaseのimportが何度も現れていることに気付き、疑問が浮かびました。

問題:同じimportを何度も行うべきか?

元のinsertUser.ts
// 全てのサーバーアクションでインポートするべきか?
import { supabase } from '@/utils/server/supabaseClient'; 

import type { User } from '@/customTypes/User';
import { handleSupabaseRequest } from '@/utils/server/handleSupabaseRequest';

export async function insertUser(userName: string): Promise<User> {
  const data = await handleSupabaseRequest( // ここで使用する
    supabase
      .from('users')
      .insert({ user_name: userName })
      .select()
      .single()
  );

  return data;
}

たかが1行のimport文ですが、
サーバーアクションが1兆個存在すれば1兆行に増えてしまうでしょう!
DRY(Don't Repeat Yourself)原則の信者である私は許すことができませんでした :(

さらに、supabaseへのアクセスは必ずsupabaseの共通関数を通すように実装したかったので、「共通関数内でこれを定義できないか?」と思いました。

そこで見つけた解決策が次のとおりです。

解決策:共通関数を使ったSupabaseクエリの管理

私が採用したのは、共通関数でsupabaseインスタンスを管理する方法です。
まず、元のsupabase共通関数は以下のようになっていました。

元のsupabase共通関数.ts
export async function handleSupabaseRequest<T>(supabaseQuery: Promise<{ data: T | null; error: any }>): Promise<T> {
  const { data } = await supabaseQuery;
  
  return data;
}

この共通関数はsupabaseQueryを引数として受け取るため、
呼び出し側でsupabaseをimportしてクエリを作成する必要があります1

そこで、共通関数側でクエリを作成できるよう、以下のように変更しました。

supabase共通関数.ts
import { supabase } from '@/utils/server/supabaseClient';

// クエリビルダー関数の型
type QueryBuilder<T> = (supabaseClient: typeof supabase) => Promise<{ data: T | null; error: any }>;

export async function handleSupabaseRequest<T>(queryBuilder: QueryBuilder<T>): Promise<T> {
  const { data, error } = await queryBuilder(supabase);

  return data as T;
}

supabaseの"クエリ作成関数"をコールバック関数とすることで、
共通関数内でsupabaseインスタンスを使い、クエリを生成できるようになりました。

insertUser.ts
import type { User } from '@/customTypes/User';
import { handleSupabaseRequest } from '@/utils/server/handleSupabaseRequest';

export async function insertUser(userName: string): Promise<User> {
  const data = await handleSupabaseRequest(async (supabase) => {
    return supabase
      .from('users')
      .insert({ user_name: userName })
      .select()
      .single();
  });

  return data;
}

これにより、呼び出し側でsupabaseのクエリを作成することなく、
コールバック関数からsupabaseのクエリビルダーを呼び出せるようになりました。

また、「supabaseのインスタンスをどこで呼び出しているのか?」と疑問に思った場合、
必然的にsupabase共通関数に行き着くため、全体像を理解しやすくなるのではと思います。

サーバーアクション側のコードも簡潔になり、エラーハンドリングも共通関数内で一度行えばよいため、今回のケースではより良いコードになりました。

ご一読いただきありがとうございました!

参考

  1. 依存性の注入の観点からは、これは好ましい状態ですが、今回の場合は全て同じsupabaseインスタンスを使用していたため、共通化を行うべきではないかと判断しました。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?