개발/go

Go로 Supabase 연동하기: 로그인부터 데이터 조회까지

xwing 2025. 5. 30. 14:56

Supabase는 Firebase의 오픈소스 대안으로, 실시간 데이터베이스, 인증, 스토리지 등을 제공하는 백엔드 서비스입니다. 이번 포스트에서는 Go 언어를 사용해 Supabase에 연결하고, 사용자 인증과 데이터 조회를 구현하는 방법을 알아보겠습니다.

사전 준비

1. Supabase 프로젝트 설정

먼저 Supabase 대시보드에서 새 프로젝트를 생성하고 다음 정보를 확인해주세요:

  • Project URL
  • Anon/Public Key
  • Service Role Key (서버사이드 작업용)

2. Go 모듈 초기화 및 의존성 설치

go mod init supabase-go-example
go get github.com/supabase-community/supabase-go
go get github.com/joho/godotenv  # 환경변수 관리용

환경변수 설정

프로젝트 루트에 .env 파일을 생성하여 Supabase 설정을 저장합니다:

SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_KEY=your-service-key

기본 클라이언트 설정

package main

import (
    "log"
    "os"
    
    "github.com/joho/godotenv"
    "github.com/supabase-community/supabase-go"
)

func initSupabase() *supabase.Client {
    // 환경변수 로드
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    
    supabaseUrl := os.Getenv("SUPABASE_URL")
    supabaseKey := os.Getenv("SUPABASE_ANON_KEY")
    
    client, err := supabase.NewClient(supabaseUrl, supabaseKey, &supabase.ClientOptions{})
    if err != nil {
        log.Fatal("Supabase 클라이언트 초기화 실패:", err)
    }
    
    return client
}

사용자 인증 구현

1. 회원가입

func signUp(client *supabase.Client, email, password string) error {
    user, err := client.Auth.SignUp(supabase.UserCredentials{
        Email:    email,
        Password: password,
    })
    
    if err != nil {
        return fmt.Errorf("회원가입 실패: %v", err)
    }
    
    fmt.Printf("회원가입 성공! 사용자 ID: %s\n", user.ID)
    return nil
}

2. 로그인

func signIn(client *supabase.Client, email, password string) (*supabase.AuthenticatedDetails, error) {
    resp, err := client.Auth.SignInWithEmailPassword(email, password)
    if err != nil {
        return nil, fmt.Errorf("로그인 실패: %v", err)
    }
    
    fmt.Printf("로그인 성공! 액세스 토큰: %s\n", resp.AccessToken)
    return &resp, nil
}

3. 로그아웃

func signOut(client *supabase.Client) error {
    err := client.Auth.SignOut()
    if err != nil {
        return fmt.Errorf("로그아웃 실패: %v", err)
    }
    
    fmt.Println("로그아웃 완료")
    return nil
}

데이터베이스 테이블 조회

1. 단순 조회

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email"`
    CreateAt string `json:"created_at"`
}

func getAllUsers(client *supabase.Client) ([]User, error) {
    var users []User
    
    data, count, err := client.From("users").Select("*", "exact", false).Execute()
    if err != nil {
        return nil, fmt.Errorf("사용자 조회 실패: %v", err)
    }
    
    err = json.Unmarshal(data, &users)
    if err != nil {
        return nil, fmt.Errorf("JSON 파싱 실패: %v", err)
    }
    
    fmt.Printf("총 %d명의 사용자를 조회했습니다.\n", count)
    return users, nil
}

2. 조건부 조회

func getUserByEmail(client *supabase.Client, email string) (*User, error) {
    var users []User
    
    data, _, err := client.From("users").
        Select("*", "exact", false).
        Eq("email", email).
        Execute()
    
    if err != nil {
        return nil, fmt.Errorf("사용자 조회 실패: %v", err)
    }
    
    err = json.Unmarshal(data, &users)
    if err != nil {
        return nil, fmt.Errorf("JSON 파싱 실패: %v", err)
    }
    
    if len(users) == 0 {
        return nil, fmt.Errorf("해당 이메일의 사용자를 찾을 수 없습니다")
    }
    
    return &users[0], nil
}

3. 정렬 및 제한

func getRecentUsers(client *supabase.Client, limit int) ([]User, error) {
    var users []User
    
    data, _, err := client.From("users").
        Select("*", "exact", false).
        Order("created_at", &supabase.OrderOpts{Ascending: false}).
        Limit(limit, "").
        Execute()
    
    if err != nil {
        return nil, fmt.Errorf("최근 사용자 조회 실패: %v", err)
    }
    
    err = json.Unmarshal(data, &users)
    if err != nil {
        return nil, fmt.Errorf("JSON 파싱 실패: %v", err)
    }
    
    return users, nil
}

데이터 삽입 및 수정

1. 데이터 삽입

func createUser(client *supabase.Client, name, email string) error {
    newUser := map[string]interface{}{
        "name":  name,
        "email": email,
    }
    
    data, _, err := client.From("users").Insert(newUser, false, "", "", "").Execute()
    if err != nil {
        return fmt.Errorf("사용자 생성 실패: %v", err)
    }
    
    fmt.Printf("사용자 생성 완료: %s\n", string(data))
    return nil
}

2. 데이터 수정

func updateUser(client *supabase.Client, userID int, name string) error {
    updateData := map[string]interface{}{
        "name": name,
    }
    
    data, _, err := client.From("users").
        Update(updateData, "", "").
        Eq("id", userID).
        Execute()
    
    if err != nil {
        return fmt.Errorf("사용자 수정 실패: %v", err)
    }
    
    fmt.Printf("사용자 수정 완료: %s\n", string(data))
    return nil
}

완전한 예제

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
    
    "github.com/joho/godotenv"
    "github.com/supabase-community/supabase-go"
)

func main() {
    // Supabase 클라이언트 초기화
    client := initSupabase()
    
    // 회원가입 예제
    err := signUp(client, "test@example.com", "password123")
    if err != nil {
        log.Printf("회원가입 오류: %v", err)
    }
    
    // 로그인 예제
    authDetails, err := signIn(client, "test@example.com", "password123")
    if err != nil {
        log.Printf("로그인 오류: %v", err)
        return
    }
    
    // 인증된 클라이언트로 테이블 조회
    users, err := getAllUsers(client)
    if err != nil {
        log.Printf("사용자 조회 오류: %v", err)
        return
    }
    
    fmt.Printf("조회된 사용자 수: %d\n", len(users))
    for _, user := range users {
        fmt.Printf("ID: %d, 이름: %s, 이메일: %s\n", 
            user.ID, user.Name, user.Email)
    }
    
    // 로그아웃
    err = signOut(client)
    if err != nil {
        log.Printf("로그아웃 오류: %v", err)
    }
}

Row Level Security (RLS) 고려사항

Supabase에서 RLS가 활성화된 테이블의 경우, 인증된 사용자만 접근할 수 있습니다. 이런 경우 다음과 같이 인증 토큰을 설정해야 합니다:

func setAuthToken(client *supabase.Client, token string) {
    client.DB.SetAuth(token)
}

// 로그인 후 토큰 설정
authDetails, err := signIn(client, email, password)
if err == nil {
    setAuthToken(client, authDetails.AccessToken)
}

에러 처리 및 베스트 프랙티스

  1. 연결 타임아웃 설정: 네트워크 문제에 대비해 적절한 타임아웃을 설정하세요.
  2. 재시도 로직: 일시적인 네트워크 오류에 대한 재시도 메커니즘을 구현하세요.
  3. 로깅: 디버깅을 위해 적절한 로깅을 추가하세요.
  4. 환경변수 보안: 프로덕션에서는 환경변수나 시크릿 관리 서비스를 사용하세요.

마무리

이번 포스트에서는 Go 언어로 Supabase를 연동하는 기본적인 방법들을 살펴봤습니다. Supabase의 강력한 기능들을 Go의 간결한 문법과 함께 사용하면 효율적인 백엔드 애플리케이션을 빠르게 개발할 수 있습니다.

추가로 실시간 구독, 파일 업로드, Edge Functions 등 Supabase의 고급 기능들도 활용해보시기 바랍니다.

궁금한 점이 있으시면 언제든 댓글로 남겨주세요!