[React 숙련] Supabase

조아영·2025년 3월 9일

◼ BaaS

웹과 모바일 앱 개발을 쉽고 빠르게 할 수 있도록 도와주는 클라우드 기반 백엔드 서비스.
BaaS를 사용하면 복잡한 백엔드 시스템을 직접 관리하지 않아도 됨.
그 결과 프론트엔드 개발에 더 집중 가능함.

웹 애플리케이션의 필수 구성 요소

웹 애플리케이션은 크게 세 부분으로 구성.

  • 프론트엔드
    사용자가 상호작용하는 영역. 화면에 보이는 모든 시각적 요소를 담당.
  • 백엔드
    서버에서 데이터 처리, 사용자 관리, 로직 처리 담당. 사용자에게 직접 보이지 않는 영역.
  • 데이터베이스
    사용자 정보, 게시글 등 데이터를 저장하는 공간. 필요할 때 데이터를 조회하는 역할 수행.

BaaS 필요 이유

프론트엔드 개발에 집중하고 싶어도 서버 설정, 보안, 인증까지 직접 처리해야 한다면 부담 큼.
서버 구축 없이 필요한 기능을 클라우드에서 바로 사용 가능함.

인기 있는 BaaS 플랫폼

  • Firebase
    Google에서 운영하는 BaaS 플랫폼
    실시간 데이터베이스, 인증, 애널리틱스 기능 제공.
  • Parse
    오픈 소스 기반 BaaS.
    커스터마이징 자유도가 높음.
  • AWS Amplify
    AWS 인프라 기반 BaaS.
    복잡한 백엔드 작업을 간편하게 처리 가능.

장단점

장점

  • 개발 속도 향상
    백엔드를 직접 구현하지 않아도 되고 이미 준비된 기능을 바로 사용 가능해 개발시간 단축가능.
  • 유지보수 간편
    서버 관리 부담이 감소해 운영과 유지보수 효율 증가.
  • 자동 확장
    사용자가 늘어나도 자동으로 스케일업 지원하니 서버 부하 걱정 없이 안정적인 서비스 제공 가능.

단점

  • 유연성 부족
    제공 기능 외 요구사항 대응 어려움.
  • 비용 예측 어려움
    사용량 증가 시 비용 상승 가능성 있음.
  • 플랫폼 의존성
    한 플랫폼에 의존하면 나중에 다른 서비스로의 이전이 어려울 수 있음.

◼ Supabase

Supabase는 PostgreSQL 기반의 오픈 소스 BaaS 플랫폼.
관계형 데이터를 기반으로 하면서 실시간 데이터 변경 감지 기능 제공.
관계형 데이터베이스 사용 시 데이터 간 관계 표현 용이함. 데이터 변경 시 UI가 자동으로 반영됨.

사용이유

1. 관계형 데이터 모델과 실무 역량

Supabase는 관계형 데이터베이스를 사용함.
실무에서는 데이터 일관성과 정확성이 중요하며 대부분의 기업 시스템은 관계형 데이터베이스 사용함.

-- 주문과 연관된 고객 정보를 함께 조회
SELECT orders.order_id, customers.name
FROM orders
JOIN customers ON orders.customer_id = customers.id
WHERE customers.region = 'Asia';

2. 실시간 기능의 실무적 활용

Firebase도 실시간 기능 제공하지만 Supabase는 관계형 데이터베이스의 장점과 결합하여 제공.
실시간 데이터 반영은 현대 웹 애플리케이션에서 필수 요소.
예를 들어 주식 거래, 실시간 대시보드, 협업 도구 등 실시간 데이터 처리가 중요한 어플리케이션 개발에 매우 유용.

// 제품 가격 변경을 실시간으로 구독하는 코드
import { supabase } from './supabaseClient';

function subscribeToPriceChanges() {
  supabase
    .from('products')
    .on('UPDATE', payload => {
      alert(`Price updated! New price: ${payload.new.price}`);
    })
    .subscribe();
}

◼ React에서 사용하기

설치 및 세팅

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

// project url
const SUPABASE_API_URL = "YOUR_SUPABASE_API_URL";

// anon public key
const SUPABASE_API_KEY = "YOUR_SUPABASE_API_KEY";

const supabase = createClient(SUPABASE_API_URL, SUPABASE_API_KEY);
export default supabase;

설치 후 Supabase 클라이언트 초기화하는 코드 작성.
이를 위해 Supabase 프로젝트의 URL과 API 키가 필요함.

데이터베이스 입력

Supabase 콘솔에서 직접 데이터베이스에 데이터 입력 가능.
웹사이트 프로젝트 대시보드에서 Table Editor를 사용해 행 추가 가능.

데이터베이스 읽기

useEffect와 useState를 사용하여 데이터 조회 처리. 조회된 데이터 화면에 출력.

// src/App.jsx

import "./App.css";
import FetchData from "./components/FetchData";

function App() {
  return (
    <>
      <h1>Supabase</h1>
      <FetchData />
    </>
  );
}

export default App;
// src/components/FetchData.jsx

import { useEffect, useState } from "react";
import supabase from "../supabaseClient";

const FetchData = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const { data, error } = await supabase.from("NACAMP_SAMPLE").select("*");
      if (error) {
        console.log("error => ", error);
      } else {
        console.log("data => ", data);
        setUsers(data);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      <h3>유저정보</h3>
      {users.map((user) => {
        return (
          <div
            key={user.id}
            style={{
              border: "1px solid black",
            }}
          >
            <h5>아이디 : {user.id}</h5>
            <h5>이름 : {user.name}</h5>
            <h5>나이 : {user.age}</h5>
            <h5>주소 : {user.address}</h5>
          </div>
        );
      })}
    </div>
  );
};

export default FetchData;

데이터베이스 쓰기

입력 폼을 통해 데이터 추가 가능. insert 메서드 사용.

// src/App.jsx

import "./App.css";
import AddData from "./components/AddData";
import FetchData from "./components/FetchData";

function App() {
  return (
    <>
      <h1>Supabase</h1>
      <FetchData />
      <AddData />
    </>
  );
}

export default App;
// src/components/AddData.jsx

import React, { useState } from "react";
import supabase from "../supabaseClient";

const AddData = () => {
  const [name, setName] = useState("");
  const [age, setAge] = useState(0);
  const [address, setAddress] = useState("");

  const handleAdd = async () => {
    const { data, error } = await supabase.from("NACAMP_SAMPLE").insert({
      name,
      age,
      address,
    });

    if (error) {
      console.log("error => ", error);
    } else {
      alert("데이터가 정상적으로 입력됐습니다.");
      console.log("data => ", data);
    }
  };

  return (
    <div
      style={{
        border: "1px solid red",
      }}
    >
      <h2>데이터 추가 로직</h2>
      <div>
        이름 :{" "}
        <input
          type="text"
          value={name}
          onChange={(e) => {
            setName(e.target.value);
          }}
        />
      </div>
      <div>
        나이 :{" "}
        <input
          type="number"
          value={age}
          onChange={(e) => {
            setAge(e.target.value);
          }}
        />
      </div>
      <div>
        주소 :{" "}
        <input
          type="text"
          value={address}
          onChange={(e) => {
            setAddress(e.target.value);
          }}
        />
      </div>
      <button onClick={handleAdd}>등록</button>
    </div>
  );
};

export default AddData;

데이터베이스 수정

특정 ID 기준 데이터 수정 가능. update와 eq 조건 사용.

// src/App.jsx

import "./App.css";
import AddData from "./components/AddData";
import FetchData from "./components/FetchData";
import UpdateData from "./components/UpdateData";

function App() {
  return (
    <>
      <h1>Supabase</h1>
      <FetchData />
      <UpdateData />
      <AddData />
    </>
  );
}

export default App;
// src/components/UpdateData.jsx

import React, { useState } from "react";
import supabase from "../supabaseClient";

const UpdateData = () => {
  const [targetId, setTargetId] = useState(0);
  const [address, setAddress] = useState("");

  const handleChange = async () => {
    const { error } = await supabase
      .from("NACAMP_SAMPLE")
      .update({
        address,
      })
      .eq("id", targetId);

    if (error) {
      console.log("error => ", error);
    }
  };

  return (
    <div
      style={{
        border: "1px solid blue",
      }}
    >
      <h2>데이터 수정 로직</h2>
      아이디 :{" "}
      <input
        type="number"
        value={targetId}
        onChange={(e) => setTargetId(e.target.value)}
      />
      <br />
      수정주소 :{" "}
      <input
        type="text"
        value={address}
        onChange={(e) => setAddress(e.target.value)}
      />
      <button onClick={handleChange}>변경</button>
    </div>
  );
};

export default UpdateData;

데이터베이스 삭제

ID 기준 데이터 삭제 처리. delete 메서드 사용.

// src/App.jsx

import "./App.css";
import AddData from "./components/AddData";
import DeleteData from "./components/DeleteData";
import FetchData from "./components/FetchData";
import UpdateData from "./components/UpdateData";

function App() {
  return (
    <>
      <h1>Supabase</h1>
      <DeleteData />
      <FetchData />
      <UpdateData />
      <AddData />
    </>
  );
}

export default App;
// src/components/DeleteData.jsx

import React, { useState } from "react";
import supabase from "../supabaseClient";

const DeleteData = () => {
  const [targetId, setTargetId] = useState(0);

  const handleDelete = async () => {
    const { error } = await supabase
      .from("NACAMP_SAMPLE")
      .delete()
      .eq("id", targetId);

    if (error) {
      console.log("error => ", error);
    }
  };

  return (
    <div
      style={{
        border: "1px solid blue",
      }}
    >
      <h2>데이터 삭제 로직</h2>
      아이디 :{" "}
      <input
        type="number"
        value={targetId}
        onChange={(e) => setTargetId(e.target.value)}
      />
      <button onClick={handleDelete}>삭제</button>
    </div>
  );
};

export default DeleteData;

0개의 댓글