๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Supabase JavaScript Client Library

2026. 5. 27. 15:46ยท๐Ÿ“‚ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

Supabase JS Client Library

 

 

โœ… Supabase JS Client Library๋ž€?


Supabase๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ „์šฉ Client Library.

 

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋ ค๋ฉด ์ด๊ฑธ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์œ ์ € ์ธ์ฆ(user authentication)์„ ์‚ฌ์šฉํ•œ๋‹ค๊ฑฐ๋‚˜ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฆฌ์Šค๋‹ํ•˜๊ฑฐ๋‚˜ Edge Function์„ ํ˜ธ์ถœํ•˜๊ฑฐ๋‚˜ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค ๋•Œ๋„ ์‚ฌ์šฉํ•œ๋‹ค.(์œ ์ €์˜ ์ฟ ํ‚ค๋ฅผ ๊ฐ€์ ธ์™€์„œ ์œ ํšจํ•œ ์„ธ์…˜์ธ์ง€ ํ™•์ธ๋„ ๊ฐ€๋Šฅ)

 

โœ… Installation


npm install @supabase/supabase-js

 

https://supabase.com/docs/reference/javascript/installing

 

JavaScript: Installing | Supabase Docs

supabase-js uses the Data API to query and mutate your Postgres data. You first need to grant Data API roles permissions to access your tables and functions. In Data API integrations settings, expose the specific tables and functions you want to access. To

supabase.com

 

 

โœ… Initialize


<app/supa-client.ts>

import { createClient } from "@supabase/supabase-js";

const client = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_PUBLISHABLE_KEY!,
);

export default client;

 

 

โœ… TypeScript Support


supabase gen types typescript --project-id abcdefghijklmnopqrst > database.types.ts

 

https://supabase.com/docs/reference/javascript/typescript-support

 

JavaScript: TypeScript support | Supabase Docs

Sometimes the generated types are not what you expect. For example, a view's column may show up as nullable when you expect it to be not null. Using type-fest, you can override the types like so:

supabase.com

 

package.json์— script๋กœ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

"db:typegen" : "supabase gen types typescript --project-id abcdefghijklmnopqrst > database.types.ts"

 

<app/supa-client.ts>

import { createClient } from "@supabase/supabase-js";
import type { Database } from "../database.types";

const client = createClient<Database>(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_PUBLISHABLE_KEY!,
);

export default client;

 

 

โœ… Usage


<community/queries.ts>

export const getTopics = async () => {
  const { data, error } = await client.from("topics").select("name, slug");
  console.log(data, error);
  return data;
};

์ž๋™ ์™„์„ฑ์œผ๋กœ query ์ž‘์„ฑ์ด ํŽธํ•ด์ง

 

export const getPosts = async () => {
  const { data, error } = await client
    .from("posts")
    .select(
      `post_id, title, created_at, topics (name), profiles (name, username, avatar)`,
    );
};

topics์™€ profiles๋ฅผ ์ˆ˜๋™์œผ๋กœ joinํ•  ํ•„์š”์—†์ด ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋œ๋‹ค.(๊ธฐ๋ณธ์œผ๋กœ left join์ด ๋˜์–ด์„œ topics์˜ name์ด๋‚˜ profiles์˜ name ๊ฐ™์€ ๊ฒƒ๋“ค์ด nullable์ด๋‹ค.)

 

null์ด ์—†๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” inner join์„ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

 

Inner Join ๋ฐฉ๋ฒ•

export const getPosts = async () => {
  const { data, error } = await client.from("posts").select(
    `post_id, 
      title, 
      created_at, 
      topics!inner (name), 
      profiles!inner (name, username, avatar)`,
  );
};

 

Column ์ด๋ฆ„ ์ง€์ •ํ•˜๋Š” ๋ฒ•

export const getPosts = async () => {
  const { data, error } = await client.from("posts").select(
    `post_id, 
      title, 
      created_at, 
      topic:topics!inner (name), 
      author:profiles!inner (name, username, avatar)`,
  );
};

 

Foreign Key Error

Error: Could not embed because more than one relationship was found for 'posts' and 'profiles'

  details: [
    {
      cardinality: 'many-to-one',
      embedding: 'posts with profiles',
      relationship: 'posts_profile_id_profiles_profile_id_fk using posts(profile_id) and profiles(profile_id)'
    },
    {
      cardinality: 'many-to-many',
      embedding: 'posts with profiles',
      relationship: 'post_upvotes using post_upvotes_post_id_posts_post_id_fk(post_id) and post_upvotes_profile_id_profiles_profile_id_fk(profile_id)'
    }
  ],

ํ˜„์žฌ posts์™€ profiles์˜ relationship์„ ์—ฐ๊ฒฐํ•˜๋ ค๋Š”๋ฐ relationship์ด 1๊ฐœ ์ด์ƒ์ด๋ผ์„œ ์ƒ๊ธฐ๋Š” ์—๋Ÿฌ์ด๋‹ค.(๋ช…ํ™•ํ•˜๊ฒŒ ์ง€์ •ํ•ด์ค˜์•ผ ํ•จ)

 

(relationship์ด 1๊ฐœ ์ด์ƒ์ธ ์ด์œ ๋Š” posts์— profile_id๋กœ profiles๊ฐ€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š” relationship๊ณผ post_upvotes์— post_id์™€ profile_id์˜ composite primary key๋กœ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š” relationship, ์ด๋ ‡๊ฒŒ 2๊ฐœ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ)

 

export const getPosts = async () => {
  const { data, error } = await client.from("posts").select(
    `post_id, 
      title, 
      created_at, 
      topic:topics!inner (name), 
      author:profiles!posts_profile_id_profiles_profile_id_fk!inner (name, username, avatar)`,
  );
  console.log(error);
  if (error) throw new Error(error.message);
  return data;
};

์ด๋ ‡๊ฒŒ ์ ์–ด์ฃผ๋ฉด ๋œ๋‹ค.(Drizzle์ด constraint๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์ž๋™ ์ƒ์„ฑํ•œ foreign key์ด๋‹ค.)

(๋‹ค๋ฅธ ๋ฐฉ๋ฒ•๋„ ์žˆ์Œ)

 

count

export const getPosts = async () => {
  const { data, error } = await client.from("posts").select(
    `post_id, 
      title, 
      created_at, 
      topic:topics!inner (name), 
      author:profiles!posts_profile_id_profiles_profile_id_fk!inner (name, username, avatar),
      upvotes:post_upvotes (count)
      `,
  );
  console.log(error);
  if (error) throw new Error(error.message);
  return data;
};

count๋งŒ ์ ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ด์ƒํ•œ ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜์ด ๋จ

์™œ ๊ตณ์ด count๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ object์— ๋‹ด๊ณ  array๋กœ ๋ฐ˜ํ™˜ํ• ๊นŒ?

๋ฐ์ดํ„ฐ ํ˜•ํƒœ๊ฐ€ ๋ง˜์— ๋“ค์ง€ ์•Š๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค view๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

 

 

โœ… View


<app/sql/views/community-post-list-view.sql>

CREATE VIEW community_post_list_view AS 
SELECT
    posts.post_id,
    posts.title,
    posts.created_at,
    topics.name AS topic,
    profiles.name AS author,
    profiles.avatar AS author_avatar,
    profiles.username AS author_username,
    COUNT(post_upvotes.post_id) AS upvotes
FROM posts
INNER JOIN topics USING (topic_id)
INNER JOIN profiles USING (profile_id)
LEFT JOIN post_upvotes USING (post_id)
GROUP BY posts.post_id, topics.name, profiles.name, profiles.avatar, profiles.username;

 

์œ„์— ์ ์€ SQL Query๋ฅผ Supabase์— views๋ผ๋Š” ํด๋”๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์•ˆ์— ๋„ฃ์€ ๋’ค ์‹คํ–‰์‹œ์ผœ ์ค€๋‹ค.

๊ทธ๋ฆฌ๊ณ  queries.ts๋ฅผ ์ˆ˜์ •ํ•ด์ค€๋‹ค.

export const getPosts = async () => {
  const { data, error } = await client
    .from("community_post_list_view")
    .select(`*`);
  console.log(error);
  if (error) throw new Error(error.message);
  return data;
};

 

ํ•˜์ง€๋งŒ type์ด null์ด ์•„๋‹ˆ์–ด์•ผ ํ•˜๋Š”๋ฐ null๋กœ ๋‚˜์˜ค๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.

-> type-fest๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

 

 

โœ… type-fest


type-fest๋ž€?

type์„ ๋ฎ์–ด ์“ฐ๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” TypeScript type์˜ ๋ชจ์Œ

 

npm install type-fest

 

https://github.com/sindresorhus/type-fest

 

GitHub - sindresorhus/type-fest: A collection of essential TypeScript types

A collection of essential TypeScript types. Contribute to sindresorhus/type-fest development by creating an account on GitHub.

github.com

 

import { createClient } from "@supabase/supabase-js";
import type { MergeDeep, SetNonNullable, SetFieldType } from "type-fest";
import type { Database as SupabaseDatabase } from "../database.types";

type Database = MergeDeep<
  SupabaseDatabase,
  {
    public: {
      Views: {
        community_post_list_view: {
          Row: SetFieldType<
            SetNonNullable<
              SupabaseDatabase["public"]["Views"]["community_post_list_view"]["Row"]
            >,
            "author_avatar",
            string | null
          >;
        };
      };
    };
  }
>;

const client = createClient<Database>(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_PUBLISHABLE_KEY!,
);

export default client;

author_avatar๋Š” nullable์ด๊ธฐ ๋•Œ๋ฌธ์— string | null๋กœ ๋ณ€๊ฒฝํ•ด์ค˜์•ผ ํ•œ๋‹ค.

'๐Ÿ“‚ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - React Router v7 - clientLoader, HydrateFallback  (0) 2026.05.29
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - React Router v7 - prefetch  (1) 2026.05.29
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Magic UI  (1) 2026.05.21
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Simple Icons  (0) 2026.05.15
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - React Router v7  (0) 2026.05.11
'๐Ÿ“‚ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - React Router v7 - clientLoader, HydrateFallback
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - React Router v7 - prefetch
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Magic UI
  • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Simple Icons
j2yonghwa
j2yonghwa
Trying to be a fullstack developer ๐Ÿš€
  • j2yonghwa
    j2yonghwa
    j2yonghwa
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (156)
      • โฐ Daily WakaTime (1)
      • ๐Ÿ–๏ธ ๋…ธ๋งˆ๋“œ์ฝ”๋” (2)
      • ๐Ÿบ Dev Setup (3)
      • ๐Ÿ”ญ Tech Info (1)
      • ๐Ÿšซ Error (1)
      • ๐Ÿ“‚ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (23)
      • ♣๏ธ Next.js 14 (10)
      • ♠๏ธ Next.js 12 (20)
      • ๐Ÿ›ธ React Native (12)
      • ๐Ÿฆ‹ TypeScript (1)
      • ๐Ÿ Python (2)
      • ๐ŸŒŠ TailwindCSS (4)
      • ๐Ÿงฉ SQL (25)
      • ๐Ÿ’Ž Prisma (5)
      • ๐ŸŒฑ MongoDB (4)
      • ๐ŸŽฏ Redis (1)
      • ๐Ÿงฌ GraphQL (2)
      • ๐Ÿ”ฅ Firebase (7)
      • ๐Ÿ’ธ Third-Party Services (2)
      • ๐Ÿ•ธ๏ธ Web (1)
      • ๐Ÿ† ์ฝ”๋”ฉํ…Œ์ŠคํŠธ (23)
      • ๐Ÿ“™ ๋ชจ๋”ฅ๋‹ค (5)
      • ๐Ÿ“— ์ฝ”ํ…Œ ํ•ฉ๊ฒฉ์ž ๋˜๊ธฐ -JS- (0)
      • ๐Ÿ“˜ ํด๋ฆฐ์ฝ”๋“œ (0)
      • ๐Ÿฏ ๊ฟ€ํŒ ๐Ÿ (1)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ํƒœ๊ทธ
    • ๋ฐฉ๋ช…๋ก
  • ๋งํฌ

    • ๊นƒํ—™
  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    PostgreSQL
    0๋ ˆ๋ฒจ
    Python
    react router
    next.js 12
    ๋ชจ๋”ฅ๋‹ค
    ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ
    API
    Next.js
    tailwindcss
    SQL
    MySQL
    ์ฝ”๋”ฉํ…Œ์ŠคํŠธ ์ž…๋ฌธ
    mongoDB
    ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
    next.js 14
    Prisma
    Firebase
    dev setup
    React Native
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
j2yonghwa
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - Supabase JavaScript Client Library
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”