01 SOFTWARE
SDK

Client

Browser와 Server 클라이언트 상세 가이드

클라이언트

01.software SDK는 사용 환경에 따라 두 가지 클라이언트를 제공합니다.

BrowserClient

브라우저 환경(Client Component)에서 사용하는 클라이언트입니다.

생성

import { createBrowserClient } from '@01.software/sdk'

const client = createBrowserClient({
  clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!
})

주요 속성

from(collection)

Supabase 스타일의 쿼리 빌더를 반환합니다.

const queryBuilder = client.from('products')

// 체이닝 방식으로 쿼리 작성
const { data } = await client
  .from('products')
  .find({ where: { status: { equals: 'published' } } })

query

React Query 훅을 제공합니다.

const { data, isLoading } = client.query.useCollection('products', {
  limit: 10
})

collections

컬렉션별 API 클라이언트를 제공합니다.

const products = await client.collections.products.find({
  limit: 10
})

설정 옵션

const client = createBrowserClient({
  // 필수: API 클라이언트 키
  clientKey: string

  // 선택: API 베이스 URL
  baseUrl?: string // 기본값: 'https://api.01.software'

  // 선택: 디버그 모드
  debug?: boolean | {
    logRequests?: boolean
    logResponses?: boolean
    logErrors?: boolean
  }

  // 선택: 재시도 설정
  retry?: {
    maxRetries?: number // 기본값: 3
    retryableStatuses?: number[] // 기본값: [408, 429, 500, 502, 503, 504]
    retryDelay?: (attempt: number) => number
  }

  // 선택: 에러 로거
  errorLogger?: {
    log: (error: Error) => void
  }
})

사용 예제

기본 사용

app/products/page.tsx
'use client'

import { client } from '@/lib/client'
import { useEffect, useState } from 'react'

export default function ProductsPage() {
  const [products, setProducts] = useState([])

  useEffect(() => {
    async function fetchProducts() {
      const { data } = await client.from('products').find({
        limit: 10,
        where: { status: { equals: 'published' } }
      })
      setProducts(data?.docs || [])
    }
    fetchProducts()
  }, [])

  return (
    <div>
      {products.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  )
}

React Query 사용 (권장)

app/products/page.tsx
'use client'

import { client } from '@/lib/client'

export default function ProductsPage() {
  const { data, isLoading, error } = client.query.useCollection('products', {
    limit: 10,
    where: { status: { equals: 'published' } }
  })

  if (isLoading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>

  return (
    <div>
      {data?.docs.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  )
}

ServerClient

서버 환경(Server Component, API Route)에서 사용하는 클라이언트입니다.

생성

import { createServerClient } from '@01.software/sdk'

const serverClient = createServerClient({
  clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!,
  secretKey: process.env.SOFTWARE_SECRET_KEY!,
})

secretKey는 절대 브라우저에 노출되어서는 안 됩니다!

주요 속성

BrowserClient의 모든 속성에 더해, 다음을 제공합니다:

api

서버 전용 API 메서드를 제공합니다.

// 주문 생성
const order = await serverClient.api.createOrder({
  paymentId: 'pay_123',
  orderNumber: generateOrderNumber(),
  email: 'user@example.com',
  orderProducts: [{
    product: 'product_id',
    variant: 'variant_id',
    quantity: 1,
    price: 10000,
  }],
  totalAmount: 10000,
})

// 주문 업데이트
await serverClient.api.updateOrder('order_id', {
  status: 'completed'
})

// 트랜잭션 업데이트
await serverClient.api.updateTransaction('transaction_id', {
  status: 'paid'
})

사용 예제

Server Component

app/products/page.tsx
import { serverClient } from '@/lib/server-client'

export default async function ProductsPage() {
  const { data } = await serverClient.from('products').find({
    limit: 10,
    where: { status: { equals: 'published' } }
  })

  return (
    <div>
      {data?.docs.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  )
}

API Route

app/api/orders/route.ts
import { serverClient } from '@/lib/server-client'
import { generateOrderNumber } from '@01.software/sdk'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()

    const order = await serverClient.api.createOrder({
      paymentId: body.paymentId,
      orderNumber: generateOrderNumber(),
      email: body.email,
      orderProducts: body.products,
      totalAmount: body.totalAmount,
    })

    return NextResponse.json({ order })
  } catch (error) {
    return NextResponse.json(
      { error: error.message },
      { status: 500 }
    )
  }
}

Server Action

app/actions/orders.ts
'use server'

import { serverClient } from '@/lib/server-client'
import { generateOrderNumber } from '@01.software/sdk'

export async function createOrder(formData: FormData) {
  const email = formData.get('email') as string
  const products = JSON.parse(formData.get('products') as string)

  const order = await serverClient.api.createOrder({
    paymentId: `pay_${Date.now()}`,
    orderNumber: generateOrderNumber(),
    email,
    orderProducts: products,
    totalAmount: products.reduce((sum, p) => sum + p.price * p.quantity, 0),
  })

  return order
}

디버그 모드

개발 중 요청/응답을 로깅하여 디버깅을 쉽게 할 수 있습니다.

전체 디버그 모드

const client = createBrowserClient({
  clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!,
  debug: true, // 모든 요청/응답/에러 로깅
})

선택적 디버그 모드

const client = createBrowserClient({
  clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!,
  debug: {
    logRequests: true,   // 요청만 로깅
    logResponses: false, // 응답 로깅 안 함
    logErrors: true,     // 에러만 로깅
  },
})

로그 출력 예시

[SDK Request] GET /api/collections/products
Query: {"limit":10,"where":{"status":{"equals":"published"}}}

[SDK Response] 200 OK (234ms)
Data: { docs: [...], pagination: {...} }

[SDK Error] Network Error
Message: Failed to fetch
Suggestion: 인터넷 연결을 확인하거나 잠시 후 다시 시도해주세요.

프로덕션 환경에서는 디버그 모드를 비활성화하세요.

재시도 로직

네트워크 오류나 서버 에러 시 자동으로 재시도합니다.

const client = createBrowserClient({
  clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!,
  retry: {
    // 최대 재시도 횟수 (기본값: 3)
    maxRetries: 3,

    // 재시도할 HTTP 상태 코드
    retryableStatuses: [408, 429, 500, 502, 503, 504],

    // 재시도 지연 시간 (Exponential backoff)
    retryDelay: (attempt) => {
      // 1초 → 2초 → 4초 → 8초 (최대 10초)
      return Math.min(1000 * 2 ** attempt, 10000)
    },
  },
})

기본 동작

  • 네트워크 에러는 항상 재시도
  • 408, 429, 500, 502, 503, 504 상태 코드는 재시도
  • 401, 404 등 클라이언트 에러는 재시도하지 않음
  • Exponential backoff 전략 사용

재시도 비활성화

const client = createBrowserClient({
  clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!,
  retry: {
    maxRetries: 0, // 재시도 비활성화
  },
})

커스텀 에러 처리

에러 발생 시 커스텀 로깅을 수행할 수 있습니다.

const client = createBrowserClient({
  clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!,
  errorLogger: {
    log: (error) => {
      // Sentry, LogRocket 등 에러 추적 서비스로 전송
      console.error('SDK Error:', error)
      // Sentry.captureException(error)
    },
  },
})

베이스 URL 변경

기본 API URL 대신 커스텀 URL을 사용할 수 있습니다.

const client = createBrowserClient({
  clientKey: process.env.NEXT_PUBLIC_SOFTWARE_CLIENT_KEY!,
  baseUrl: 'https://custom-api.example.com',
})

대부분의 경우 기본 베이스 URL(https://api.01.software)을 사용하면 됩니다.

타입 안전성

모든 클라이언트 메서드는 완전한 타입 추론을 제공합니다.

// 타입이 자동으로 추론됨
const { data } = await client.from('products').find()
// data의 타입: { docs: Product[], pagination: PaginationInfo } | undefined

const { data: product } = await client.from('products').findById('id')
// product의 타입: Product | undefined

// 생성 시에도 타입 체크
await client.from('products').create({
  name: '상품명',     // ✅ OK
  price: 10000,      // ✅ OK
  invalid: 'field',  // ❌ 타입 에러
})

다음 단계

On this page