Drizzle์™€ Expo ์‹œ์ž‘ํ•˜๊ธฐ

This guide assumes familiarity with:
  • Expo SQLite - SQLite API๋ฅผ ํ†ตํ•ด ์ฟผ๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ - ์—ฌ๊ธฐ์„œ ์ฝ์–ด๋ณด๊ธฐ

1๋‹จ๊ณ„ - Expo ํ…œํ”Œ๋ฆฟ์œผ๋กœ ํ”„๋กœ์ ํŠธ ์„ค์ •ํ•˜๊ธฐ

npm
yarn
pnpm
bun
npx create expo-app --template blank-typescript

์ด ํ…œํ”Œ๋ฆฟ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ํŒŒ์ผ ๊ตฌ์กฐ

ํ…œํ”Œ๋ฆฟ์„ ์„ค์น˜ํ•˜๊ณ  db ํด๋”๋ฅผ ์ถ”๊ฐ€ํ•œ ํ›„, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. db/schema.ts ํŒŒ์ผ์—๋Š” Drizzle ํ…Œ์ด๋ธ” ์ •์˜๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. drizzle ํด๋”์—๋Š” SQL ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ๊ณผ ์Šค๋ƒ…์ƒท์ด ๋“ค์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ฆ <project root>
 โ”œ ๐Ÿ“‚ assets
 โ”œ ๐Ÿ“‚ drizzle
 โ”œ ๐Ÿ“‚ db
 โ”‚  โ”” ๐Ÿ“œ schema.ts
 โ”œ ๐Ÿ“œ .gitignore
 โ”œ ๐Ÿ“œ .npmrc
 โ”œ ๐Ÿ“œ app.json
 โ”œ ๐Ÿ“œ App.tsx
 โ”œ ๐Ÿ“œ babel.config.ts
 โ”œ ๐Ÿ“œ drizzle.config.ts
 โ”œ ๐Ÿ“œ package.json
 โ”” ๐Ÿ“œ tsconfig.json

2๋‹จ๊ณ„ - expo-sqlite ํŒจํ‚ค์ง€ ์„ค์น˜

npm
yarn
pnpm
bun
npx expo install expo-sqlite

์œ„ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•ด expo-sqlite ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒจํ‚ค์ง€๋Š” Expo ํ”„๋กœ์ ํŠธ์—์„œ SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

3๋‹จ๊ณ„ - ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ์„ค์น˜ํ•˜๊ธฐ

npm
yarn
pnpm
bun
npm i drizzle-orm
npm i -D drizzle-kit

์œ„ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ drizzle-orm๊ณผ drizzle-kit ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. drizzle-kit์€ ๊ฐœ๋ฐœ ์˜์กด์„ฑ์œผ๋กœ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

4๋‹จ๊ณ„ - Drizzle ORM์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐํ•˜๊ธฐ

๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ์— App.tsx ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค:

import * as SQLite from 'expo-sqlite';
import { drizzle } from 'drizzle-orm/expo-sqlite';

const expo = SQLite.openDatabaseSync('db.db');

const db = drizzle(expo);

์ด ์ฝ”๋“œ๋Š” Expo SQLite๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ์„ ์—ด๊ณ , Drizzle ORM์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. db ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4๋‹จ๊ณ„ - ํ…Œ์ด๋ธ” ์ƒ์„ฑํ•˜๊ธฐ

db ๋””๋ ‰ํ† ๋ฆฌ์— schema.ts ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ํ…Œ์ด๋ธ”์„ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค:

import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";

export const usersTable = sqliteTable("users_table", {
  id: int().primaryKey({ autoIncrement: true }),  // ์ž๋™ ์ฆ๊ฐ€ํ•˜๋Š” ๊ธฐ๋ณธ ํ‚ค
  name: text().notNull(),                         // ์ด๋ฆ„ (ํ•„์ˆ˜)
  age: int().notNull(),                           // ๋‚˜์ด (ํ•„์ˆ˜)
  email: text().notNull().unique(),               // ์ด๋ฉ”์ผ (ํ•„์ˆ˜, ๊ณ ์œ ๊ฐ’)
});

์ด ์ฝ”๋“œ๋Š” SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— users_table์ด๋ผ๋Š” ํ…Œ์ด๋ธ”์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ด๋ธ”์€ id, name, age, email ์ปฌ๋Ÿผ์œผ๋กœ ๊ตฌ์„ฑ๋˜๋ฉฐ, ๊ฐ ์ปฌ๋Ÿผ์€ ํƒ€์ž…๊ณผ ์ œ์•ฝ ์กฐ๊ฑด์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

5๋‹จ๊ณ„ - Drizzle ์„ค์ • ํŒŒ์ผ ์„ค์ •ํ•˜๊ธฐ

Drizzle ์„ค์ • ํŒŒ์ผ - Drizzle Kit์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์„ค์ • ํŒŒ์ผ๋กœ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ •๋ณด, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํด๋”, ์Šคํ‚ค๋งˆ ํŒŒ์ผ ๋“ฑ์— ๋Œ€ํ•œ ๋ชจ๋“  ์ •๋ณด๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— drizzle.config.ts ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  dialect: 'sqlite', // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐฉ์–ธ ์„ค์ • (SQLite ์‚ฌ์šฉ)
  driver: 'expo', // ๋“œ๋ผ์ด๋ฒ„ ์„ค์ • (Expo ์‚ฌ์šฉ)
  schema: './db/schema.ts', // ์Šคํ‚ค๋งˆ ํŒŒ์ผ ๊ฒฝ๋กœ
  out: './drizzle', // ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์ด ์ƒ์„ฑ๋  ๊ฒฝ๋กœ
});

์ด ์„ค์ • ํŒŒ์ผ์€ Drizzle Kit์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. dialect๋Š” ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ข…๋ฅ˜๋ฅผ, driver๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•  ๋•Œ ์‚ฌ์šฉํ•  ๋“œ๋ผ์ด๋ฒ„๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. schema๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๊ฐ€ ์ •์˜๋œ ํŒŒ์ผ์˜ ๊ฒฝ๋กœ๋ฅผ, out์€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์ด ์ €์žฅ๋  ๊ฒฝ๋กœ๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

6๋‹จ๊ณ„ - metro ์„ค์ •ํ•˜๊ธฐ

๋ฃจํŠธ ํด๋”์— metro.config.js ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค:

const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
config.resolver.sourceExts.push('sql');
module.exports = config;

์ด ์ฝ”๋“œ๋Š” Expo์˜ ๊ธฐ๋ณธ Metro ์„ค์ •์„ ๊ฐ€์ ธ์™€์„œ SQL ํŒŒ์ผ ํ™•์žฅ์ž๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •์„ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.

7๋‹จ๊ณ„ - babel ์„ค์ • ์—…๋ฐ์ดํŠธ

babel.config.js
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: [["inline-import", { "extensions": [".sql"] }]] // <-- ์ด ๋ถ€๋ถ„ ์ถ”๊ฐ€
  };
};

8๋‹จ๊ณ„ - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ณ€๊ฒฝ ์‚ฌํ•ญ ์ ์šฉํ•˜๊ธฐ

Expo๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” drizzle-kit generate ์ปค๋งจ๋“œ๋ฅผ ํ†ตํ•ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ƒ์„ฑํ•œ ํ›„, drizzle-orm์˜ migrate() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ๋Ÿฐํƒ€์ž„์— ์ ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑํ•˜๊ธฐ:

npx drizzle-kit generate

9๋‹จ๊ณ„ - ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ ์šฉ ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌํ•˜๊ธฐ:

App.tsx ํŒŒ์ผ์— ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๊ณผ ์‚ฌ์šฉ์ž ์ƒ์„ฑ, ์ฝ๊ธฐ, ์—…๋ฐ์ดํŠธ, ์‚ญ์ œ๋ฅผ ์œ„ํ•œ ์ฟผ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

import { Text, View } from 'react-native';
import * as SQLite from 'expo-sqlite';
import { useEffect, useState } from 'react';
import { drizzle } from 'drizzle-orm/expo-sqlite';
import { usersTable } from './db/schema';
import { useMigrations } from 'drizzle-orm/expo-sqlite/migrator';
import migrations from './drizzle/migrations';

const expo = SQLite.openDatabaseSync('db.db');

const db = drizzle(expo);

export default function App() {
  const { success, error } = useMigrations(db, migrations);
  const [items, setItems] = useState(null);

  useEffect(() => {
    if (!success) return;

    (async () => {
      await db.delete(usersTable);

      await db.insert(usersTable).values([
        {
          name: 'John',
          age: 30,
          email: 'john@example.com',
        },
      ]);

      const users = await db.select().from(usersTable);
      setItems(users);
    })();
  }, [success]);

  if (error) {
    return (
      <View>
        <Text>๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์˜ค๋ฅ˜: {error.message}</Text>
      </View>
    );
  }

  if (!success) {
    return (
      <View>
        <Text>๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ง„ํ–‰ ์ค‘...</Text>
      </View>
    );
  }

  if (items === null || items.length === 0) {
    return (
      <View>
        <Text>๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</Text>
      </View>
    );
  }

  return (
    <View
      style={{
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        width: '100%',
        height: '100%',
        justifyContent: 'center',
      }}
    >
      {items.map((item) => (
        <Text key={item.email}>{item.email}</Text>
      ))}
    </View>
  );
}

์ด ์ฝ”๋“œ๋Š” SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ ์šฉํ•˜๊ณ , ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑ, ์‚ญ์ œ, ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

10๋‹จ๊ณ„ - Expo ์•ฑ ์‚ฌ์ „ ๋นŒ๋“œ ๋ฐ ์‹คํ–‰

npm
yarn
pnpm
bun
npx expo run:ios