첫 번째 앱 만들기
간단한 Todo 앱을 만들면서 Connect Base의 핵심 기능을 배워봅니다.
🚦 사전 준비 (Prerequisites)
코드 작성 전에 콘솔에서 두 가지를 발급받아야 합니다:
| 항목 | 위치 | 형식 |
|---|---|---|
| Public Key | 콘솔 → 앱 → 설정 → Public Keys | cb_pk_xxx |
| Todo 테이블 ID | 콘솔 → 앱 → 데이터베이스 → 새 테이블 | tbl_xxx |
발급 절차가 처음이라면 먼저 빠른 시작 가이드를 확인하세요.
테이블 스키마 (콘솔에서 만들 컬럼)
| 컬럼명 | 타입 | 필수 |
|---|---|---|
title | string | ✓ |
completed | boolean | ✓ (기본값 false) |
id,created_at,updated_at컬럼은 Connect Base가 자동으로 추가합니다.
1. 프로젝트 설정
bash
# Vite + React + TypeScript 프로젝트 생성
npm create vite@latest my-todo-app -- --template react-ts
cd my-todo-app
# Connect Base SDK 설치
npm install connectbase-client2. 환경 변수 설정
.env 파일을 생성합니다:
bash
VITE_CONNECT_BASE_PUBLIC_KEY=cb_pk_your_public_key_here
VITE_TODO_TABLE_ID=tbl_your_table_id_here💡 Vite에서는
VITE_접두사가 붙어야 클라이언트에서 접근 가능합니다.
3. SDK 설정
src/lib/connectbase.ts 파일을 생성합니다:
typescript
import ConnectBase from 'connectbase-client'
export const cb = new ConnectBase({
publicKey: import.meta.env.VITE_CONNECT_BASE_PUBLIC_KEY
})
export const TODO_TABLE_ID = import.meta.env.VITE_TODO_TABLE_ID4. Todo 타입 정의
src/types/todo.ts:
typescript
export interface Todo {
id: string
title: string
completed: boolean
created_at: string
updated_at: string
}5. Todo 컴포넌트
src/App.tsx:
tsx
import { useState, useEffect } from 'react'
import { ApiError } from 'connectbase-client'
import { cb, TODO_TABLE_ID } from './lib/connectbase'
import type { Todo } from './types/todo'
function App() {
const [todos, setTodos] = useState<Todo[]>([])
const [newTitle, setNewTitle] = useState('')
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
// Todo 목록 조회
useEffect(() => {
loadTodos()
}, [])
const loadTodos = async () => {
try {
setLoading(true)
const result = await cb.database.queryData(TODO_TABLE_ID, {
orderBy: 'created_at',
orderDirection: 'desc',
limit: 100,
})
// result.data 는 DataItem[] — 각 row는 { id, data: {...}, created_at, ... } 구조
const items = result.data.map((row) => ({
id: row.id,
title: row.data.title as string,
completed: row.data.completed as boolean,
created_at: row.created_at,
updated_at: row.updated_at,
}))
setTodos(items)
} catch (e) {
setError(e instanceof ApiError ? e.message : '목록 조회 실패')
} finally {
setLoading(false)
}
}
// Todo 추가
const addTodo = async () => {
if (!newTitle.trim()) return
try {
await cb.database.createData(TODO_TABLE_ID, {
data: { title: newTitle, completed: false },
})
setNewTitle('')
await loadTodos()
} catch (e) {
setError(e instanceof ApiError ? e.message : '추가 실패')
}
}
// Todo 완료 토글
const toggleTodo = async (todo: Todo) => {
try {
await cb.database.updateData(TODO_TABLE_ID, todo.id, {
data: { completed: !todo.completed },
})
await loadTodos()
} catch (e) {
setError(e instanceof ApiError ? e.message : '수정 실패')
}
}
// Todo 삭제
const deleteTodo = async (id: string) => {
try {
await cb.database.deleteData(TODO_TABLE_ID, id)
await loadTodos()
} catch (e) {
setError(e instanceof ApiError ? e.message : '삭제 실패')
}
}
return (
<div style={{ maxWidth: 480, margin: '40px auto', padding: 16 }}>
<h1>My Todos</h1>
{error && (
<div style={{ color: 'red', marginBottom: 12 }}>
⚠️ {error}
</div>
)}
<div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
<input
type="text"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addTodo()}
placeholder="새 할 일..."
style={{ flex: 1, padding: 8 }}
/>
<button onClick={addTodo}>추가</button>
</div>
{loading && <p>불러오는 중...</p>}
{!loading && todos.length === 0 && (
<p>아직 할 일이 없습니다. 첫 번째 할 일을 추가해보세요!</p>
)}
<ul style={{ listStyle: 'none', padding: 0 }}>
{todos.map((todo) => (
<li
key={todo.id}
style={{
display: 'flex',
alignItems: 'center',
gap: 8,
padding: 8,
borderBottom: '1px solid #eee',
}}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none', flex: 1 }}>
{todo.title}
</span>
<button onClick={() => deleteTodo(todo.id)}>삭제</button>
</li>
))}
</ul>
</div>
)
}
export default App6. 실행
bash
npm run dev브라우저에서 http://localhost:5173 을 열면 Todo 앱이 실행됩니다.
완성된 기능
- ✅ Todo 추가 (
createData) - ✅ Todo 목록 조회 (
queryData+ orderBy) - ✅ Todo 완료 토글 (
updateData) - ✅ Todo 삭제 (
deleteData) - ✅
ApiError기반 에러 처리 - ✅ 로딩 / 빈 상태 UI
다음 단계
- 실시간 동기화 추가 — 여러 기기에서 실시간 동기화
- 사용자 인증 추가 — 로그인/회원가입
- 파일 첨부 — 이미지 업로드
- 흔한 에러 & 해결책