AsyncStorage로 부족한 관계형 데이터나 대량 데이터 처리에는 SQLite가 적합하고, 반응형 쿼리가 필요하면 WatermelonDB가 좋습니다.

To-do 앱 수준을 넘어 오프라인 우선 앱, 대량 데이터 검색, 관계형 데이터를 다루려면 로컬 데이터베이스가 필요합니다.


expo-sqlite (Expo 프로젝트)

BASH
npx expo install expo-sqlite
TSX
import * as SQLite from 'expo-sqlite';

// 데이터베이스 열기
const db = await SQLite.openDatabaseAsync('myapp.db');

// 테이블 생성
await db.execAsync(`
  CREATE TABLE IF NOT EXISTS todos (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    completed INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now'))
  );
`);

// 삽입
await db.runAsync(
  'INSERT INTO todos (title) VALUES (?)',
  '장보기'
);

// 조회
const todos = await db.getAllAsync<{
  id: number;
  title: string;
  completed: number;
}>('SELECT * FROM todos WHERE completed = ?', 0);

// 업데이트
await db.runAsync(
  'UPDATE todos SET completed = 1 WHERE id = ?',
  todoId
);

// 삭제
await db.runAsync('DELETE FROM todos WHERE id = ?', todoId);

Repository 패턴

TSX
// database/todoRepository.ts
class TodoRepository {
  private db: SQLite.SQLiteDatabase;

  constructor(db: SQLite.SQLiteDatabase) {
    this.db = db;
  }

  async getAll() {
    return this.db.getAllAsync<Todo>('SELECT * FROM todos ORDER BY created_at DESC');
  }

  async getById(id: number) {
    return this.db.getFirstAsync<Todo>('SELECT * FROM todos WHERE id = ?', id);
  }

  async create(title: string) {
    const result = await this.db.runAsync(
      'INSERT INTO todos (title) VALUES (?)',
      title
    );
    return result.lastInsertRowId;
  }

  async toggleComplete(id: number) {
    await this.db.runAsync(
      'UPDATE todos SET completed = NOT completed WHERE id = ?',
      id
    );
  }

  async delete(id: number) {
    await this.db.runAsync('DELETE FROM todos WHERE id = ?', id);
  }

  async search(query: string) {
    return this.db.getAllAsync<Todo>(
      'SELECT * FROM todos WHERE title LIKE ?',
      `%${query}%`
    );
  }
}

WatermelonDB — 반응형 데이터베이스

WatermelonDB는 SQLite 위에 구축된 고성능 반응형 데이터베이스입니다. 데이터가 변경되면 관련 컴포넌트가 자동으로 리렌더링됩니다.

BASH
npm install @nozbe/watermelondb
npm install -D @babel/plugin-proposal-decorators

스키마 정의

TSX
// database/schema.ts
import { appSchema, tableSchema } from '@nozbe/watermelondb';

export const schema = appSchema({
  version: 1,
  tables: [
    tableSchema({
      name: 'posts',
      columns: [
        { name: 'title', type: 'string' },
        { name: 'body', type: 'string' },
        { name: 'is_published', type: 'boolean' },
        { name: 'created_at', type: 'number' },
      ],
    }),
    tableSchema({
      name: 'comments',
      columns: [
        { name: 'body', type: 'string' },
        { name: 'post_id', type: 'string', isIndexed: true },
        { name: 'created_at', type: 'number' },
      ],
    }),
  ],
});

모델 정의

TSX
// database/models/Post.ts
import { Model } from '@nozbe/watermelondb';
import { field, text, date, children } from '@nozbe/watermelondb/decorators';

export class Post extends Model {
  static table = 'posts';
  static associations = {
    comments: { type: 'has_many' as const, foreignKey: 'post_id' },
  };

  @text('title') title!: string;
  @text('body') body!: string;
  @field('is_published') isPublished!: boolean;
  @date('created_at') createdAt!: Date;
  @children('comments') comments!: any;
}

반응형 쿼리와 컴포넌트 연결

TSX
import { withObservables } from '@nozbe/watermelondb/react';

// 데이터 변경 시 자동 리렌더링
const enhance = withObservables([], ({ database }) => ({
  posts: database.get('posts').query().observe(),
}));

function PostList({ posts }: { posts: Post[] }) {
  return (
    <FlatList
      data={posts}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={{ padding: 16 }}>
          <Text style={{ fontWeight: 'bold' }}>{item.title}</Text>
          <Text numberOfLines={2}>{item.body}</Text>
        </View>
      )}
    />
  );
}

export default enhance(PostList);

저장소 선택 가이드

요구사항추천 솔루션
키-값 설정 저장AsyncStorage / MMKV
간단한 관계형 데이터expo-sqlite
대량 데이터 + 검색SQLite
반응형 쿼리 + 동기화WatermelonDB
오프라인 우선 앱WatermelonDB

정리

  • SQLite: 관계형 데이터와 복잡한 쿼리가 필요할 때 적합합니다
  • WatermelonDB: 대량 데이터 + 반응형 UI + 서버 동기화가 필요할 때 강력합니다
  • 데이터베이스 접근은 Repository 패턴 으로 추상화하면 유지보수가 편합니다
  • 대부분의 앱은 AsyncStorage/MMKV + React Query(서버 동기화)로 충분합니다
댓글 로딩 중...