0Pricing
React Native Academy · 강의

Supabase 클라이언트로 데이터베이스 조회하기

Supabase 클라이언트로 Postgres 테이블의 행을 선택, 삽입, 수정 및 삭제하고, eq와 gte로 필터를 적용하며 행 수준 보안 정책을 처리합니다.

Supabase 클라이언트로 데이터베이스 조회하기은(는) CoddyKit의 무료 React Native Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 React Native Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. React Native Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

The Supabase Query Builder

The Supabase JS client exposes a query builder that wraps the PostgREST API, letting you build SQL-like queries in JavaScript. You start every query with supabase.from('table_name') and chain methods like .select(), .insert(), .update(), and .delete().

All queries return a Promise that resolves to { data, error }. Always check error before using data — if the query fails, data will be null.

const { data, error } = await supabase
  .from('posts')
  .select('*');

if (error) {
  console.error(error.message);
} else {
  console.log(data); // Array of post objects
}

Selecting Specific Columns

Instead of fetching all columns with select('*'), you can specify only the columns you need. This reduces data transfer and is especially important on mobile networks where bandwidth is limited.

You can also select columns from related tables using embedded relationships. Supabase infers the join from your foreign key definitions, so select('title, author:profiles(username)') performs a join and nests the profile data inside each post object.

// Select specific columns
const { data } = await supabase
  .from('posts')
  .select('id, title, created_at');

// Select with a join
const { data: postsWithAuthor } = await supabase
  .from('posts')
  .select('title, content, author:profiles(username, avatar_url)');

// Each item in postsWithAuthor looks like:
// { title: 'Hello', content: '...', author: { username: 'alice', avatar_url: '...' } }

Filtering Rows with eq and Other Filters

The query builder provides a rich set of filter methods that map directly to SQL WHERE clauses. The most common are .eq(column, value) for exact equality, .neq for not-equal, .gt and .lt for comparisons, and .ilike for case-insensitive text search.

You can chain multiple filters — they are combined with AND by default. To use OR logic, use the .or() method.

// Exact match
const { data } = await supabase
  .from('posts')
  .select('*')
  .eq('user_id', userId);

// Greater than a date
const { data: recent } = await supabase
  .from('posts')
  .select('*')
  .gt('created_at', '2024-01-01');

// Case-insensitive search
const { data: results } = await supabase
  .from('posts')
  .select('*')
  .ilike('title', '%react native%');

Ordering, Limiting, and Pagination

Use .order(column, { ascending: false }) to sort results, .limit(n) to cap the number of rows returned, and .range(from, to) for cursor-based pagination.

Pagination is essential for mobile lists. A common pattern is to fetch 20 items at a time and load more when the user reaches the end of the list (using FlatList's onEndReached callback).

const PAGE_SIZE = 20;

async function fetchPosts(page: number) {
  const from = page * PAGE_SIZE;
  const to = from + PAGE_SIZE - 1;

  const { data, error } = await supabase
    .from('posts')
    .select('id, title, created_at')
    .order('created_at', { ascending: false })
    .range(from, to);

  return data ?? [];
}

Inserting Rows

To add a new record, use .insert() and pass an object or an array of objects. Supabase inserts the rows and returns the inserted data if you chain .select() after the insert.

You do not need to provide columns with database defaults (like id or created_at) — Postgres fills them in. For columns that reference auth.uid() through RLS policies, it is good practice to set the user_id explicitly to the current user's ID.

async function createPost(title: string, content: string, userId: string) {
  const { data, error } = await supabase
    .from('posts')
    .insert({ title, content, user_id: userId })
    .select()
    .single(); // returns a single object instead of an array

  if (error) {
    console.error('Insert error:', error.message);
    return null;
  }

  return data; // The newly created post row
}

Updating Rows

Use .update() chained with a filter to modify specific rows. Without a filter, .update() would attempt to update every row in the table — a dangerous operation that Supabase blocks by default for safety.

Always combine .update() with a filter like .eq('id', postId) to target exactly the row you want to change. Chain .select().single() to receive the updated row in the response.

async function updatePost(postId: string, newTitle: string) {
  const { data, error } = await supabase
    .from('posts')
    .update({ title: newTitle, updated_at: new Date().toISOString() })
    .eq('id', postId)
    .select()
    .single();

  if (error) {
    console.error('Update error:', error.message);
    return null;
  }

  return data; // Updated post
}

Deleting Rows

The .delete() method removes rows that match the filter. Like .update(), it requires a filter — you cannot delete all rows without explicitly using a condition like .neq('id', '').

To implement a soft delete (hiding items without removing them from the database), add an is_deleted boolean column and use .update({ is_deleted: true }) instead of .delete(). Filter queries to exclude soft-deleted rows with .eq('is_deleted', false).

// Hard delete
async function deletePost(postId: string) {
  const { error } = await supabase
    .from('posts')
    .delete()
    .eq('id', postId);

  if (error) {
    console.error('Delete error:', error.message);
  }
}

// Soft delete
async function softDeletePost(postId: string) {
  const { error } = await supabase
    .from('posts')
    .update({ is_deleted: true })
    .eq('id', postId);
}

Using Supabase in useEffect

The most common pattern in React Native is to fetch data inside a useEffect hook and store it in state. Create an async function inside the effect, call it immediately, and clean up any ongoing subscriptions in the return function.

Track loading and error state alongside the data so your UI can show an ActivityIndicator while the request is in flight and an error message if it fails.

import { useEffect, useState } from 'react';
import { supabase } from '../lib/supabase';

export function usePostsList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    async function fetchPosts() {
      const { data, error } = await supabase
        .from('posts')
        .select('*')
        .order('created_at', { ascending: false });

      if (error) setError(error.message);
      else setPosts(data ?? []);
      setLoading(false);
    }

    fetchPosts();
  }, []);

  return { posts, loading, error };
}

RLS and User-Scoped Queries

With Row Level Security enabled, you do not need to filter by user_id in every query — the database policy handles it. When an authenticated user calls supabase.from('posts').select('*'), Supabase automatically applies the RLS policy and returns only that user's rows.

This means your queries stay clean and simple, and the security boundary is enforced at the database level rather than relying on client-side filtering, which could be bypassed.

// With RLS policy: USING (auth.uid() = user_id)
// This query returns ONLY the current user's posts automatically
const { data: myPosts } = await supabase
  .from('posts')
  .select('*')
  .order('created_at', { ascending: false });

// No need to add .eq('user_id', userId) — RLS handles it
// If you were to query without being authenticated,
// RLS would return zero rows

Upsert: Insert or Update

The .upsert() method combines INSERT and UPDATE: if the row's primary key already exists, it updates the row; otherwise it inserts a new one. This is useful for profile syncing where you want to create a profile on first sign-in and update it on subsequent calls.

Pass { onConflict: 'id' } to tell Supabase which column to check for conflicts. The ignoreDuplicates option (when set to true) skips the row silently instead of updating it.

async function upsertProfile(userId: string, username: string) {
  const { error } = await supabase
    .from('profiles')
    .upsert(
      { id: userId, username, updated_at: new Date().toISOString() },
      { onConflict: 'id' }
    );

  if (error) {
    console.error('Upsert error:', error.message);
  }
}

Counting Rows Efficiently

To get a row count without fetching all data, pass { count: 'exact', head: true } to .select(). The head: true option tells PostgREST to return only headers, not the actual rows, making this query extremely fast even on large tables.

The count is available on the count property of the returned object rather than data. Use this for displaying total item counts in badges or list headers.

const { count, error } = await supabase
  .from('posts')
  .select('*', { count: 'exact', head: true })
  .eq('user_id', userId);

console.log('Total posts:', count); // e.g. 42

Quick Check

Test your understanding of React Native Mobile Development concepts from this lesson.

Lesson Recap

In this lesson you learned: how to use the Supabase query builder for SELECT, INSERT, UPDATE, and DELETE, how Row Level Security automatically scopes queries to the authenticated user, and how to paginate and filter results efficiently. Next up we explore real-time subscriptions to receive live data updates from Supabase.

자주 묻는 질문

“Supabase 클라이언트로 데이터베이스 조회하기” 강의는 무료인가요?

네 — “Supabase 클라이언트로 데이터베이스 조회하기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 React Native Academy 강의 전체를 잠금 해제할 수 있습니다. React Native Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“Supabase 클라이언트로 데이터베이스 조회하기”에서 뭘 배우나요?

Supabase 클라이언트로 Postgres 테이블의 행을 선택, 삽입, 수정 및 삭제하고, eq와 gte로 필터를 적용하며 행 수준 보안 정책을 처리합니다. 브라우저에서 직접 실행하는 실습 코드로 React Native Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

React Native Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 React Native Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.

“Supabase 클라이언트로 데이터베이스 조회하기” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 React Native Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 React Native Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. React Native에서 Supabase 클라이언트 설정하기
  2. 이메일 및 OAuth 인증
  3. Supabase 클라이언트로 데이터베이스 조회하기
  4. 실시간 구독
← React Native Academy(으)로 돌아가기