Drizzle <> Expo SQLite

**๊ณต์‹ ์›น์‚ฌ์ดํŠธ**์— ๋”ฐ๋ฅด๋ฉด, Expo๋Š” React Native์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœ, ๋นŒ๋“œ ๋ฐ ๋ฐฐํฌํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ ์ƒํƒœ๊ณ„์ž…๋‹ˆ๋‹ค. Hermes JavaScript ๋Ÿฐํƒ€์ž„๊ณผ Metro ๋ฒˆ๋“ค๋Ÿฌ๋กœ ๊ตฌ๋™๋˜๋ฉฐ, Drizzle Expo ๋“œ๋ผ์ด๋ฒ„๋Š” ๋‘ ๊ฐ€์ง€๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•˜๋„๋ก ๊ตฌ์ถ•๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Drizzle ORM์€ Expo SQLite๋ฅผ ์œ„ํ•œ ์ตœ๊ณ ์˜ ํˆดํ‚ท์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

npm
yarn
pnpm
bun
npm i drizzle-orm expo-sqlite@next
npm i -D drizzle-kit
import { drizzle } from "drizzle-orm/expo-sqlite";
import { openDatabaseSync } from "expo-sqlite";

const expo = openDatabaseSync("db.db");
const db = drizzle(expo);

await db.select().from(users);

Live Queries

useLiveQuery ํ›…์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  Drizzle ์ฟผ๋ฆฌ๋ฅผ ๋ฐ˜์‘ํ˜•์œผ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

import { useLiveQuery, drizzle } from 'drizzle-orm/expo-sqlite';
import { openDatabaseSync } from 'expo-sqlite';
import { Text } from 'react-native';
import * as schema from './schema';

const expo = openDatabaseSync('db.db', { enableChangeListener: true }); // <-- ๋ณ€๊ฒฝ ๋ฆฌ์Šค๋„ˆ ํ™œ์„ฑํ™”
const db = drizzle(expo);

const App = () => {
  // ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์ž๋™์œผ๋กœ ๋‹ค์‹œ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค
  const { data } = useLiveQuery(db.select().from(schema.users));
  return <Text>{JSON.stringify(data)}</Text>;
};

export default App;

Drizzle Kit๋ฅผ ์‚ฌ์šฉํ•œ Expo SQLite ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

Drizzle Kit๋ฅผ SQL ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง„ํ–‰ํ•˜๊ธฐ ์ „์— Drizzle ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”. Expo / React Native๋Š” SQL ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์•ฑ์— ๋ฒˆ๋“ค๋กœ ํฌํ•จํ•ด์•ผ ํ•˜๋ฉฐ, ์ €ํฌ๊ฐ€ ์ด๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

babel ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜

SQL ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ๋ฌธ์ž์—ด๋กœ ์ง์ ‘ ๋ฒˆ๋“ค์— ํฌํ•จํ•˜๋ ค๋ฉด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

npm install babel-plugin-inline-import

์„ค์ • ํŒŒ์ผ ์—…๋ฐ์ดํŠธ

babel.config.js, metro.config.js ๋ฐ drizzle.config.ts ํŒŒ์ผ์„ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

babel.config.js
module.exports = function(api) {
  api.cache(true);

  return {
    presets: ['babel-preset-expo'],
    plugins: [["inline-import", { "extensions": [".sql"] }]] // <-- ์ด ๋ถ€๋ถ„์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”
  };
};
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;

Drizzle Kit ์„ค์ •์— dialect: 'sqlite' ๋ฐ driver: 'expo'๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”

drizzle.config.ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
	schema: './db/schema.ts',
	out: './drizzle',
  dialect: 'sqlite',
	driver: 'expo', // <--- ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค
});

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ

SQL ์Šคํ‚ค๋งˆ ํŒŒ์ผ๊ณผ drizzle.config.ts ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ ํ›„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

npx drizzle-kit generate

์•ฑ์— ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ถ”๊ฐ€

์ด์ œ ./drizzle ํด๋”์—์„œ migrations.js ํŒŒ์ผ์„ Expo/React Native ์•ฑ์œผ๋กœ ๊ฐ€์ ธ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ์ปค์Šคํ…€ useMigrations ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ›…์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์›ํ•˜๋Š” ๋Œ€๋กœ useEffect ํ›…์—์„œ ์ˆ˜๋™์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

App.tsx
import { drizzle } from "drizzle-orm/expo-sqlite";
import { openDatabaseSync } from "expo-sqlite";
import { useMigrations } from 'drizzle-orm/expo-sqlite/migrator';
import migrations from './drizzle/migrations';

const expoDb = openDatabaseSync("db.db");

const db = drizzle(expoDb);

export default function App() {
  const { success, error } = useMigrations(db, migrations);

  if (error) {
    return (
      <View>
        <Text>Migration error: {error.message}</Text>
      </View>
    );
  }

  if (!success) {
    return (
      <View>
        <Text>Migration is in progress...</Text>
      </View>
    );
  }

  return ...your application component;
}