チュートリアル: React Nativeアプリケーションの PowerSync への移行
項目一覧
- フェーズ 1: 準備とセットアップ
- Atlas クラスターの配置
- IPアクセス リストへの PowerSync IP の追加
- サンプル データのインポート
- PowerSync を設定する
- 同期されたデータの表示
- フェーズ 2: 移行用のRealmアプリを準備する
- 最初のプロジェクトの複製
- Device Syncプロジェクトをローカルのみに拒否
- 変更の実行と確認
- フェーズ 3: Realmから PowerSync クライアントへの移行
- 依存関係のインストール
- データ スキーマの移行
- リファクター アプリケーション コード
- 変更の実行と確認
- フェーズ 4: Atlas から PowerSync へのデータの同期
- 初期設定
- クライアント コードのリファクター
- 変更の実行と確認
- フェーズ 5: バックエンドAPIの実装
- 検査コネクター
- アップロード メソッドを実装する
- バックエンド サーバーの作成
- 変更の実行と確認
- フェーズ 6: 最後の処理とクリーンアップ
- すべて表示トグルを実装する(任意)
- オフラインモードの切り替えを実装する(任意)
- クリーンアップ プロジェクト
- 結果と次のステップ
年 9 月2024 日現在、Atlas Device SDK(Realm)、 Device Sync、App Services は非推奨になりました。つまり、これらのサービスのユーザーは 9 月 2025 までに別のソリューションに移行する必要があります。 さらに時間が必要な場合は、 サポートにお問い合わせください 。
PowerSync は、 Atlas Device Syncの上位代替手段です。 これは SQLite ベースのソリューションであり、 Device Sync を使用しているモバイルアプリケーションがある場合は移行するのに適したソリューションになる可能性があります。
このチュートリアルでは、 React Nativeで記述されたDevice Syncモバイルアプリケーション をPowerSync に移行するために必要な手順についてガイド。 バックエンドデータは Atlas に保持されるため、PowerSync サービスの構成、ローカルデータベースのスキーマとバインディングの更新、Atlas への書き込み用のバックエンドサービスを設定する必要があります。
このチュートリアルでは、 邦土 Powersync例リポジトリで利用可能なReact NativeのRealm Todo リストアプリケーションを使用します。2
フェーズ 1: 準備とセットアップ
Atlas クラスターの配置
まず、Atlas クラスターをデプロイしてテスト データを入力する必要があります。 これは、Atlas を初めて設定する場合と同様にガイドます。 すでに配置されているクラスターは、自由にスキップしてください。
MongoDB Atlasに移動し、Atlas アカウントに登録するか、すでにアカウントがある場合はサインインします。
次に、クラスターを作成します。
テスト目的では、デフォルト設定の M0(無料)クラスターを選択します。 ニーズに合わせて追加の変更を行ってください。
[ 配置の作成 ] をクリックします。
ダッシュボードに返されます。 [ クラスターへの接続 ] モーダルが自動的に表示されます。
[ 接続方法の選択 ] をクリックし、[ ドライバー ] を選択します。
この画面から、ステップ 3 に表示されるURLをコピーします。
接続文字列をアプリケーションコードに追加します。これは接続文字列です。 MongoDBインスタンスにアクセスするには必要です。 将来の参照のために接続文字列を保存します。
次の手順では、PowerSyncインスタンスがデータベースに接続するために使用するユーザー名とパスワードを作成します。
[ Done ] をクリックしてモーダルを閉じます。
クラスターのデプロイが完了すると、 ダッシュボードは次のようになります。
新しいデータベースを作成するには、 [データの追加] をクリックします。
[ Atlas でのデータベースの作成 ] カードから、 [ START ] をクリックします。
PowerSync というデータベースと、Item というコレクションを作成し、[データベースの作成] をクリックします。
ダッシュボードに返され、新しく作成されたデータベースとコレクションが表示されます。
最後に、PowerSync がこのデータベースに接続するために使用する新しいユーザーを作成する必要があります。
左側のサイドバーで、[ セキュリティ ] 見出しの下の [ データベース アクセス ] をクリックします。
[ 新しいデータベースユーザーの追加 ] をクリックし、 という新しいユーザーを作成し、パスワードを入力します。
powersync
先ほどコピーした接続文字列で使用するユーザー名とパスワードに注意してください。注意
ユーザー名またはパスワードに次の特殊文字のいずれかが含まれている場合は、接続文字列のURLセーフな形式に変換する必要があります 。
$
、:
、/
、?
、!
、#
、[
、]
、@
。 これは手動で行うことも、urlcoder.org などのURLエンコードアプリケーションを使用することもできます。[ データベースユーザー特権 ] セクションで、[ 特定の特権の追加
readWrite
]dbAdmin
をクリックし、 PowerSyncデータベースの ロールと ロールの特権を追加します。[ Add User ] をクリックします。
必要なデータベース権限を持つ新しく作成されたユーザーが表示されます。
ユーザー権限の詳細については、 PowerSync ソース データベース セットアップガイドの「MongoDB 」セクションを参照してください。
IPアクセス リストへの PowerSync IP の追加
PowerSync が Atlas で実行中データベースにアクセスするには、 IP アクセス リストにサービスIPアドレスを追加する必要があります。 これらのIPアドレスは、 PowerSync のセキュリティとIPフィルタリングのドキュメントに記載されています。
左側のサイドバーで、[ セキュリティ ] 見出しの下の [ ネットワーク アクセス ] をクリックします。
[+ IPアドレスの追加] をクリックし、 IPアドレスを入力します。将来このリストを管理するユーザーをより支援するために、オプションのコメントとして PowerSync を入力することも推奨します。
[Confirm(確認)] をクリックし、各IPに対して を繰り返します。
サンプル データのインポート
まだ行っていない場合は、データベースユーザーのユーザー名とパスワードを使用して、以前にコピーした接続文字列のプレースホルダーを更新します。
この手順では、後の手順でデータを同期するために使用されるサンプルデータをインポートします。
まず、 MongoDB Database Tools をインストールして mongoimport
にアクセスできるようにします。 詳しくは、ご利用中のオペレーティング システム用のインストールガイドを参照してください。
database-tools
をインストールした後、ターミナルに以下を入力して、mongoimport
にアクセスできることを確認します。
mongoimport --version
これにより、ツールのバージョンが返されます。 問題が発生した場合は、上記の インストールガイドを参照してください。
次に、次の内容で sample.json
というJSONファイルを作成します。
[ { "isComplete": false, "summary": "Complete project documentation", "owner_id": "mockUserId" }, { "isComplete": true, "summary": "Buy groceries", "owner_id": "mockUserId" }, { "isComplete": false, "summary": "Schedule dentist appointment", "owner_id": "mockUserId" }, { "isComplete": false, "summary": "Prepare presentation for next week", "owner_id": "mockUserId" }, { "isComplete": true, "summary": "Pay utility bills", "owner_id": "mockUserId" }, { "isComplete": false, "summary": "Fix bug in login system", "owner_id": "mockUserId2" }, { "isComplete": false, "summary": "Call mom", "owner_id": "mockUserId" }, { "isComplete": true, "summary": "Submit expense reports", "owner_id": "mockUserId2" }, { "isComplete": false, "summary": "Plan team building event", "owner_id": "mockUserId2" }, { "isComplete": false, "summary": "Review pull requests", "owner_id": "mockUserId2" } ]
このサンプルデータには、いくつかの To Do リスト項目が含まれています。 owner_id
は、このチュートリアルの後半のフィルタリング例に使用されます。
このJSON をインポートするには、次のコマンドを入力して、<connection-string>
プレースホルダーを接続文字列に置き換えます。
mongoimport --uri="<connection-string>" --db=PowerSync --collection=Item --file=sample.json --jsonArray
次のメッセージが表示されます。
10 document(s) imported successfully. 0 document(s) failed to import.
そうでない場合は、コマンド パラメータ(接続文字列を含む)が正しいこと、および Atlas ユーザーが正しいデータベースアクセス権を持っていることを確認します。
挿入されたドキュメントは、 Atlas UIでコレクションに移動するか、 MongoDB Compassビジュアル デスクトップアプリケーションを使用して表示および管理できます。 MongoDB Compassを通じてデータベースとコレクションを表示および管理するには、同じ接続文字列 を使用して接続する必要があります。

PowerSync を設定する
ここで、PowerSync に移動し、登録またはサインインします。
初めてサインインする場合は、開始するために新しいインスタンスを作成する必要があります。
TodoList
という新しいインスタンスを作成します。

接続データベースとしてMongoDB を選択します。

Atlas接続文字列を使用して接続設定に入力します。
重要
ユーザー名、パスワード、またはその他のURLパラメーターを含まない、接続文字列の短縮バージョンを使用します。 例、接続は mongodb+srv://m0cluster.h6folge.mongodb.net/
のようになります。
前の手順でこのアカウントに割り当てたデータベース名(「PowerSync」)、ユーザー名(「Powersync」)、およびパスワードを入力します。

[ 接続をテストする ] をクリックして、正常に接続されることを確認します。
次のエラーが表示された場合は、必要な PowerSync サービスの IP がすべて Atlas IP アクセス リストにあることを確認してください。

まだ問題が発生している場合は、「 MongoDB接続用の PowerSync データベース接続ガイド 」を参照してください。
[ Next(次へ) ] をクリックして、新しい PowerSyncインスタンスを配置します。これが完了するまでに数分かかる場合があります。
インスタンスが配置された後、いくつかの基本的な同期ルールを作成することで、移行されたデータを表示できるようになります。
まず、デフォルトの同期ルールを削除し、以下のように置き換えます。
bucket_definitions: user_buckets: parameters: SELECT request.user_id() as user_id data: - SELECT _id as id, * FROM "Item" WHERE bucket.user_id = 'global' OR owner_id = bucket.user_id
PowerSync サービスに正しく同期するアイテムについては、次の点に注意してください。
_id
はid
にマッピングする必要があります。コレクション名 "Item" は引用符で囲む必要があります。 これは、コレクション名が大文字で始まるためです。
ユーザー固有のバケットは、データベース全体へのアクセスを提供する
global
のuser_id
に一致する必要があります。 それ以外の場合は、認証トークンから検索される、指定されたuser_id
と照合します。
PowerSync 同期ルールは非常に深いトピックであることに注意してください。 詳細については、この 同期ルール ブログ記事 または PowerSync 同期ルール ドキュメント を参照してください。
[ Save and Deploy(保存と配置) ] をクリックします。また、配置が完了するまでにかなりの時間がかかる場合があります。
配置が完了すると、次のものが表示されます。

配置が完了すると、適切なステータスが表示されます。
PowerSync
エラーが発生した場合は、 ユーザーが PowerSync ソース データベース設定 のドキュメントに記載されている権限で設定されていることを確認してください。
[ インスタンスの管理 ] をクリックして、同期ルールと配置ステータスを確認します。
同期されたデータの表示
この設定を完了するには、 PowerSync 診断アプリを使用して、同期ルールに追加して追加した To Do リストの項目を表示します。 このツールを使用するには、まず開発トークンを作成する必要があります。
PowerSync ページの上部にある [
Manage Instances
] をクリックします。左側のサイドバーで、TodoList の横にある省略記号(...)をクリックして、このインスタンスのコンテキスト メニューを開き、[インスタンスの編集] を選択します。
[ クライアント認証 ]タブを選択し、[ 開発トークンを有効にする ] をクリックします。
[ Save(保存) ] をクリックして を配置します。

TodoList の横にある省略記号(...)をクリックして、このインスタンスのコンテキスト メニューを再度開き、 [開発トークンの生成] を選択します。
トークンの subject/user_id を指定するよう求められます。 これは user_id
として機能し、これを実行するように同期ルールを設定できます。
以前に定義した同期ルールでは、 subject/user_id を global
に設定して、データセット全体へのアクセス権を持つトークンを生成できます。 これを mockUserId
または mockUserId2
に設定して、特定の owner_id
で同期することもできます。
生成されたトークンをコピーし、診断アプリを開き、開発トークンを貼り付けます。
注意
開発トークンは 12 時間で期限切れになります。 診断ツールの有効期限が切れると Atlas との同期が停止するため、同期を再開するには新しいトークンを生成する必要があります。
このページのようなページが表示されます。

左側のサイドバーで[ SQL Console ] をクリックします。
すべてのアイテムを表示するには、SELECT
クエリを作成します。
SELECT * FROM Item

これで、 MongoDBデータベース をモバイルアプリケーションに同期するために必要なサービスがすべてできました。
フェーズ 2: 移行用のRealmアプリを準備する
最初のプロジェクトの複製
このフェーズでは、 React NativeのRealm Todo リストアプリケーションをクローンします。 例リポジトリのmain
ブランチには、移行の最終結果が含まれています。
例リポジトリを使用してこのガイドに従うには、00-Start-Here
ブランチをチェックアウトしてください。
git clone https://github.com/takameyer/realm2powersync cd realm2powersync git checkout 00-Start-Here
次に、依存関係をインストールして、エディターがすべてのインポートを選択できるようにし、このプロジェクトを編集する際にエラーが発生しないようにします。
重要
このチュートリアルでは、 Node.jsの最新バージョンがインストールされていることを前提としています。
npm install
アプリケーションでは、アクティブなDevice Syncサービスを持つ Atlas クラスターが存在することが前提とされているため、まだ実行できません。 次の手順では、プロジェクトをローカル専用アプリケーションとして実行するために必要な変更を加えます。
Device Syncプロジェクトをローカルのみに拒否
Atlas Device Sync部分は、アプリケーションがローカルのみのデータで実行中ようにする必要があります。
まず、source/AppWrapper.txs
を開き、AppProvider
、UserProvider
、sync
の構成を削除します。
更新された AppWrapper.txs
ファイルは次のようになります。
import React from 'react'; import { StyleSheet, View, ActivityIndicator } from 'react-native'; import { RealmProvider } from '@realm/react'; import { App } from './App'; import { Item } from './ItemSchema'; const LoadingIndicator = () => { return ( <View style={styles.activityContainer}> <ActivityIndicator size="large" /> </View> ); }; export const AppWrapper = () => { return ( <RealmProvider schema={[Item]} fallback={LoadingIndicator}> <App /> </RealmProvider> ); }; const styles = StyleSheet.create({ activityContainer: { flex: 1, flexDirection: 'row', justifyContent: 'space-around', padding: 10, }, });
次に、source/App.tsx
を開き、dataExplorerLink
に関する部分と OfflineMode
と Logout
のヘッダー ボタンを削除します(これは後で実装されます)。
更新された App.tsx
ファイルは次のようになります。
import React from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { StyleSheet, Text, View } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { LogoutButton } from './LogoutButton'; import { ItemListView } from './ItemListView'; import { OfflineModeButton } from './OfflineModeButton'; const Stack = createStackNavigator(); const headerRight = () => { return <OfflineModeButton />; }; const headerLeft = () => { return <LogoutButton />; }; export const App = () => { return ( <> {/* All screens nested in RealmProvider have access to the configured realm's hooks. */} <SafeAreaProvider> <NavigationContainer> <Stack.Navigator> <Stack.Screen name="Your To-Do List" component={ItemListView} options={{ headerTitleAlign: 'center', //headerLeft, //headerRight, }} /> </Stack.Navigator> </NavigationContainer> <View style={styles.footer}> <Text style={styles.footerText}> Log in with the same account on another device or simulator to see your list sync in real time. </Text> </View> </SafeAreaProvider> </> ); }; const styles = StyleSheet.create({ footerText: { fontSize: 12, textAlign: 'center', marginVertical: 4, }, hyperlink: { color: 'blue', }, footer: { paddingHorizontal: 24, paddingVertical: 12, }, });
最後に、source/ItemListView.tsx
を開き、次の更新を行います。
フレキシブルな同期サブスクライブコードを削除する
ユーザーをモックユーザーに置き換え: -
const user={ id: 'mockUserId' };
dataExplorerer
参照をすべて削除Show All Tasks
スイッチの機能を排除します(これは後で実装されます)
更新された ItemListView.tsx
ファイルは次のようになります。
import React, { useCallback, useState, useEffect } from 'react'; import { BSON } from 'realm'; import { useRealm, useQuery } from '@realm/react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { Alert, FlatList, StyleSheet, Switch, Text, View } from 'react-native'; import { Button, Overlay, ListItem } from '@rneui/base'; import { CreateToDoPrompt } from './CreateToDoPrompt'; import { Item } from './ItemSchema'; import { colors } from './Colors'; export function ItemListView() { const realm = useRealm(); const items = useQuery(Item).sorted('_id'); const user = { id: 'mockUserId' }; const [showNewItemOverlay, setShowNewItemOverlay] = useState(false); const [showAllItems, setShowAllItems] = useState(true); // createItem() takes in a summary and then creates an Item object with that summary const createItem = useCallback( ({ summary }: { summary: string }) => { // if the realm exists, create an Item realm.write(() => { return new Item(realm, { summary, owner_id: user?.id, }); }); }, [realm, user], ); // deleteItem() deletes an Item with a particular _id const deleteItem = useCallback( (id: BSON.ObjectId) => { // if the realm exists, get the Item with a particular _id and delete it const item = realm.objectForPrimaryKey(Item, id); // search for a realm object with a primary key that is an objectId if (item) { if (item.owner_id !== user?.id) { Alert.alert("You can't delete someone else's task!"); } else { realm.write(() => { realm.delete(item); }); } } }, [realm, user], ); // toggleItemIsComplete() updates an Item with a particular _id to be 'completed' const toggleItemIsComplete = useCallback( (id: BSON.ObjectId) => { // if the realm exists, get the Item with a particular _id and update it's 'isCompleted' field const item = realm.objectForPrimaryKey(Item, id); // search for a realm object with a primary key that is an objectId if (item) { if (item.owner_id !== user?.id) { Alert.alert("You can't modify someone else's task!"); } else { realm.write(() => { item.isComplete = !item.isComplete; }); } } }, [realm, user], ); return ( <SafeAreaProvider> <View style={styles.viewWrapper}> <View style={styles.toggleRow}> <Text style={styles.toggleText}>Show All Tasks</Text> <Switch trackColor={{ true: '#00ED64' }} onValueChange={() => { setShowAllItems(!showAllItems); }} value={showAllItems} /> </View> <Overlay isVisible={showNewItemOverlay} overlayStyle={styles.overlay} onBackdropPress={() => setShowNewItemOverlay(false)}> <CreateToDoPrompt onSubmit={({ summary }) => { setShowNewItemOverlay(false); createItem({ summary }); }} /> </Overlay> <FlatList keyExtractor={item => item._id.toString()} data={items} renderItem={({ item }) => ( <ListItem key={`${item._id}`} bottomDivider topDivider> <ListItem.Title style={styles.itemTitle}> {item.summary} </ListItem.Title> <ListItem.Subtitle style={styles.itemSubtitle}> <Text>{item.owner_id === user?.id ? '(mine)' : ''}</Text> </ListItem.Subtitle> <ListItem.Content> {!item.isComplete && ( <Button title="Mark done" type="clear" onPress={() => toggleItemIsComplete(item._id)} /> )} <Button title="Delete" type="clear" onPress={() => deleteItem(item._id)} /> </ListItem.Content> </ListItem> )} /> <Button title="Add To-Do" buttonStyle={styles.addToDoButton} onPress={() => setShowNewItemOverlay(true)} /> </View> </SafeAreaProvider> ); } const styles = StyleSheet.create({ viewWrapper: { flex: 1, }, sectionContainer: { marginTop: 32, paddingHorizontal: 24, }, addToDoButton: { backgroundColor: colors.primary, borderRadius: 4, margin: 5, }, completeButton: { backgroundColor: colors.primary, borderRadius: 4, margin: 5, }, showCompletedButton: { borderRadius: 4, margin: 5, }, showCompletedIcon: { marginRight: 5, }, itemTitle: { flex: 1, }, itemSubtitle: { color: '#979797', flex: 1, }, toggleRow: { flexDirection: 'row', alignItems: 'center', padding: 12, }, toggleText: { flex: 1, fontSize: 16, }, overlay: { backgroundColor: 'white', }, status: { width: 40, height: 40, justifyContent: 'center', borderRadius: 5, borderWidth: 1, borderColor: '#d3d3d3', backgroundColor: '#ffffff', alignSelf: 'flex-end', }, delete: { alignSelf: 'flex-end', width: 65, marginHorizontal: 12, }, statusCompleted: { borderColor: colors.purple, }, statusIcon: { textAlign: 'center', fontSize: 17, color: colors.purple, }, });
これらの変更により、アプリはローカルデータベースに対して動作するようになります。
変更の実行と確認
移行を開始する前に、更新されたアプリケーションをビルドして実行し、意図したとおりに動作することを確認する必要があります。
iOSの場合は、次のコマンドを実行します。
npx pod-install npm run ios
Android の場合は、次のコマンドを実行します。
npm run android
ビルド エラーはすべて、このドキュメントの範囲外であることに注意してください。 ビルド関連の問題が発生している場合は、 React Native のドキュメントを参照して環境が正しく設定されていることを確認してください。
アプリがを実行中間に、基本的な機能を確認できます。 次のことができる必要があります。
新しい項目の作成
項目を完了としてマークします
アイテムを削除

フェーズ 3: Realmから PowerSync クライアントへの移行
依存関係のインストール
ローカル専用のRealmアプリケーションで を実行中できたら、このアプリケーションを変換して、 PowerSyncクライアントのローカル専用バージョンを使用できます。
PowerSync は SQLite ベースのデータベースを使用するため、互換性を確保するためにスキーマにいくつか変更を加える必要があります。
これを実現するには、 PowerSyncクライアント を設定する必要があります。 詳しくは、 @Powersync/react-native npmリポジトリまたは PowerSync React Nativeセットアップのドキュメント を参照してください。
まず、次のコマンドを実行して、PowerSync React Nativeクライアント、バッキング SQLiteデータベース、非同期イテレータ ポリゴン(手順ごとに必要)、および bson
依存関係(挿入用の ObjectId`` の生成に使用)の依存関係を追加します。 MongoDBへのドキュメント):
npm install @powersync/react-native @journeyapps/react-native-quick-sqlite @azure/core-asynciterator-polyfill bson
ポリゴンを設定するには、index.js
を開き、ファイルの先頭に import '@azure/core-asynciterator-polyfill';
を追加します。
更新された index.js
ファイルは次のようになります。
import '@azure/core-asynciterator-polyfill'; import 'react-native-get-random-values'; import {AppRegistry} from 'react-native'; import {AppWrapper} from './source/AppWrapper'; import {name as appName} from './app.json'; AppRegistry.registerComponent(appName, () => AppWrapper);
依存関係が追加されたので、アプリケーションを再構築する必要があります。
iOSの場合は、
pod-install
を実行します。Android の場合、
react-native-quick-sqlite
と互換性を持たせるために、必要な最小 SDK を 24 に更新します。 そのためには、android/build.gradle
を開き、minSdkVersion
を 21 から 24 に変更します。
データ スキーマの移行
ここで、 ローカルデータベースのデータ型とスキーマを設定します。
特定のスキーマを設定する方法については、 PowerSync MongoDBタイプ マッピング のドキュメントを参照してください。以下は、使用可能なタイプのクイック参照です。
タイプ | 説明 |
---|---|
null | 未定義、または設定されていない値 |
integer | 64 ビットの符号付き整数 |
real | 64 ビットの浮動点数 |
text | UTF-8 テキスト string |
blob | バイナリ データ |
このチュートリアルでは、source/ItemSchema.tsx
を次のように変更します。
import {column, Schema, Table} from '@powersync/react-native'; export const ItemSchema = new Table({ isComplete: column.integer, summary: column.text, owner_id: column.text, }); export const AppSchema = new Schema({ Item: ItemSchema, }); export type Database = (typeof AppSchema)['types']; export type Item = Database['Item'];
重要
Schema
に渡されるプロパティ名は、ローカル テーブルとMongoDBコレクションの名前を表します。 この場合は、名前が Item
であることを確認してください。
このコードでは、型を手動で定義する代わりに、AppSchema
から直接エクスポートすることに注意してください。
リファクター アプリケーション コード
PowerSync にアクセスしてデータをバインドするには、 PowerSyncクライアントのフックとプロバイダーにアクセスする必要があります。この機能は、 PowerSyncContext コンポーネントを通じて提供されます。
まず、source/AppWrapper.tsx
を更新して PowerSyncContext
を使用し、 PowerSyncクライアントを初期化します。
import React from 'react'; import {App} from './App'; import {AppSchema} from './ItemSchema'; import {PowerSyncContext, PowerSyncDatabase} from '@powersync/react-native'; const powerSync = new PowerSyncDatabase({ schema: AppSchema, database: { dbFilename: 'powersync.db', }, }); powerSync.init(); export const AppWrapper = () => { return ( <PowerSyncContext.Provider value={powerSync}> <App /> </PowerSyncContext.Provider> ); };
次に、PowerSyncクライアント を使用するように ItemListView.tsx
を更新します。 これを実現するには、このコンポーネントの上部で使用されているフックをアップデートする必要があります。
書き込みや更新を行うためにローカルデータベースにアクセスするには、
usePowerSync
フックを使用します。更新時に自動的に再レンダリングされる Todo リスト項目の一覧を取得するには、
useQuery
フックを使用します。
次の変更を行います。
import { BSON } from 'realm';
を削除Add
import { ObjectId } from 'bson';
ItemListView
関数の最初の 2 行を次のように変更します。export function ItemListView() { const db = usePowerSync(); const {data: items} = useQuery<Item>('SELECT * FROM Item');
次に、createItem
、deleteItem
、toggleItemIsComplete
のメソッドを更新する必要があります。
これらのメソッドのそれぞれで、usePowerSync
から返された db
オブジェクトを使用します。 Realmと同様に、ローカルデータベースはトランザクションを開き、挿入、更新、削除などの可変操作を実行します。 また、アプリケーションのフロントエンドにエラーを伝達するために、try/catch ブロックも追加します。
各アイテムの一意の ID を作成するために、コードは bson
から ObjectId
をインポートしていることに注意してください。 PowerSync では、プライマリキーアイテムの名前は id
であることが想定されていることに注意してください。
作成コードは、このロジックに直接項目のデフォルト値も実装します。 この場合、isComplete
は false に初期化され、id
は新しく作成された ObjectId
の string 結果で初期化されます。
createItem
メソッドは次のように実装できます。
// createItem() takes in a summary and then creates an Item object with that summary const createItem = useCallback( async ({summary}: {summary: string}) => { try { // start a write transaction to insert the new Item db.writeTransaction(async tx => { await tx.execute( 'INSERT INTO Item (id, summary, owner_id, isComplete) VALUES (?, ?, ?, ?)', [new ObjectId().toHexString(), summary, user?.id, false], ); }); } catch (ex: any) { Alert.alert('Error', ex?.message); } }, [db], );
deleteItem
メソッドと toggleItemIsComplete
メソッドは似ているため、次のように実装します。
// deleteItem() deletes an Item with a particular _id const deleteItem = useCallback( async (id: String) => { // start a write transaction to delete the Item try { db.writeTransaction(async tx => { await tx.execute('DELETE FROM Item WHERE id = ?', [id]); }); } catch (ex: any) { Alert.alert('Error', ex?.message); } }, [db], ); // toggleItemIsComplete() updates an Item with a particular _id to be 'completed' const toggleItemIsComplete = useCallback( async (id: String) => { // start a write transaction to update the Item try { db.writeTransaction(async tx => { await tx.execute( 'UPDATE Item SET isComplete = NOT isComplete WHERE id = ?', [id], ); }); } catch (ex: any) { Alert.alert('Error', ex?.message); } }, [db], );
最後に、レンダリングされた FlatList
をアップデートします。 次の操作を行います。
_id
のインスタンスをid
に置き換えFlatList
のkeyExtractor
を更新して、id
string を直接使用します。以前は、データベースは
ObjectId
を返していました。 これは string に変換する必要があります。
アップデートされた FlatList
は次のようになります。
<FlatList keyExtractor={item => item.id} data={items} renderItem={({item}) => ( <ListItem key={`${item.id}`} bottomDivider topDivider> <ListItem.Title style={styles.itemTitle}> {item.summary} </ListItem.Title> <ListItem.Subtitle style={styles.itemSubtitle}> <Text>{item.owner_id === user?.id ? '(mine)' : ''}</Text> </ListItem.Subtitle> <ListItem.Content> <Pressable accessibilityLabel={`Mark task as ${ item.isComplete ? 'not done' : 'done' }`} onPress={() => toggleItemIsComplete(item.id)} style={[ styles.status, item.isComplete && styles.statusCompleted, ]}> <Text style={styles.statusIcon}> {item.isComplete ? '✓' : '○'} </Text> </Pressable> </ListItem.Content> <ListItem.Content> <Pressable accessibilityLabel={'Remove Item'} onPress={() => deleteItem(item.id)} style={styles.delete}> <Text style={[styles.statusIcon, {color: 'blue'}]}> DELETE </Text> </Pressable> </ListItem.Content> </ListItem> )} />
変更の実行と確認
コードの更新が完了すると、ローカル PowerSyncクライアント を使用できるようになります。
確認するには、アプリケーションを再ビルドします 。 iOS を使用している場合は、npx pod-install
を使用してポッドを更新することを忘れないでください。

これで、PowerSync を使用して Todo リスト項目を追加、更新、削除できる動作するアプリケーションが完成しました。
問題が発生した場合は、例リポジトリの02 -Migrate-local-Client ブランチでその点までに行われた変更を表示できます。
これで、モバイルアプリケーションがMongoDBからリアルタイムでデータを同期する準備が整いました。
注意
Realmデータがまだ移行されていないことに注意してください。 このガイドでは、 Atlas でホストされているMongoDBクラスターがデータのソースであると想定し、これをアプリケーションに同期します。 ローカル データの移行は、このチュートリアルの範囲外ですが、将来のドキュメントで対処される可能性があります。
フェーズ 4: Atlas から PowerSync へのデータの同期
初期設定
これで、PowerSync 診断ツールを使用して検証された、Atlas からの同期されたデータを含む実行中のPowerSync サービスが作成されています。
このフェーズでは、このデータを取得してReact Nativeアプリケーションに同期します。
開始するには、トークンとエンドポイントのいくつかの環境変数を設定する方法を作成する必要があります。
まず、開発依存関係に react-native-dotenv
をインストールします。 これは、プロジェクトのルートから .env
ファイルを受け取り、環境変数をアプリケーションに直接インポートできるようにする Bagel プラグインです。
npm install -D react-native-dotenv
次に、babel.config.js
ファイルに次の行を追加します。
module.exports = { presets: ['module:metro-react-native-babel-preset'], plugins: ['module:react-native-dotenv'], };
types
という新しいディレクトリを作成し、その中にインポートする次の変数を含む env.d.ts
という名前の新しいファイルを作成します。
declare module '@env' { export const AUTH_TOKEN: string; export const POWERSYNC_ENDPOINT: string; }
環境変数に必要な値は、PowerSync から取得する必要があります。
PowerSync コンソールで、左側のバーで [TodoList] の横にある [] をクリックしてコンテキスト メニューを開きます。
[ インスタンスの編集 ] を選択します。
URL をコピーして保存します。

次に、 subject/user_idmockUserId
を使用してインスタンスの新しい開発トークンを生成します。生成されたトークンをコピーして保存します。
アプリケーションプロジェクトから、 ルートディレクトリに .env
ファイルを作成し、作成した PowerSync エンドポイントとトークンを貼り付けます。
POWERSYNC_ENDPOINT=<endpoint> AUTH_TOKEN=<dev-token>
クライアント コードのリファクター
PowerSyncインスタンスに接続できるように、アプリケーションを若干リファクタリングする必要があります。
まず、source
に PowerSync.ts
という新しいファイルを作成し、以下を貼り付けます。
import { AppSchema } from './ItemSchema'; import { AbstractPowerSyncDatabase, PowerSyncDatabase, } from '@powersync/react-native'; import { AUTH_TOKEN, POWERSYNC_ENDPOINT } from '@env'; const powerSync = new PowerSyncDatabase({ schema: AppSchema, database: { dbFilename: 'powersync.db', }, }); powerSync.init(); class Connector { async fetchCredentials() { return { endpoint: POWERSYNC_ENDPOINT, token: AUTH_TOKEN, }; } async uploadData(database: AbstractPowerSyncDatabase) { console.log('Uploading data'); } } export const setupPowerSync = (): PowerSyncDatabase => { const connector = new Connector(); powerSync.connect(connector); return powerSync; }; export const resetPowerSync = async () => { await powerSync.disconnectAndClear(); setupPowerSync(); };
このファイルは、次の処理を行います。
新しい
Connector
クラスを作成します。このクラスは、 PowerSyncクライアントで開発トークンと PowerSync エンドポイントを設定するために使用されます。モックアウトされた
uploadData
関数を定義します。この関数は次のフェーズで変更を Atlas にプッシュするために使用されます。PowerSyncクライアント を設定およびリセットするためのメソッドを定義します。 クライアントをリセットすると、行われた変更はキューに配置されるため、現時点では開発に役立ちます。 これらの変更が処理されるまで、新しい更新は受信されません。
次に、AppWrapper.tsx
を更新して新しい setupPowerSync
メソッドを使用します。
import { PowerSyncContext } from '@powersync/react-native'; import React from 'react'; import { App } from './App'; import { setupPowerSync } from './PowerSync'; const powerSync = setupPowerSync(); export const AppWrapper = () => { return ( <PowerSyncContext.Provider value={powerSync}> <App /> </PowerSyncContext.Provider> ); };
次に、LogoutButton.tsx
をリファクタリングして resetPowerSync
メソッドを実装します。 名前を ResetButton.tsx
に変更し、内容を次のように更新します。
import React, { useCallback } from 'react'; import { Pressable, Alert, View, Text, StyleSheet } from 'react-native'; import { colors } from './Colors'; import { resetPowerSync } from './PowerSync'; export function ResetButton() { const signOut = useCallback(() => { resetPowerSync(); }, []); return ( <Pressable onPress={() => { Alert.alert('Reset Database?', '', [ { text: 'Yes, Reset Database', style: 'destructive', onPress: () => signOut(), }, { text: 'Cancel', style: 'cancel' }, ]); }}> <View style={styles.buttonContainer}> <Text style={styles.buttonText}>Reset</Text> </View> </Pressable> ); } const styles = StyleSheet.create({ buttonContainer: { paddingHorizontal: 12, }, buttonText: { fontSize: 16, color: colors.primary, }, });
次に、App.tsx
を変更して、ヘッダーの左側に Reset
ボタンを表示します。
import { LogoutButton } from './LogoutButton';
をimport { ResetButton } from './ResetButton';
に置き換えheaderLeft
で、既存の行をreturn <ResetButton />;
に置き換えます//headerLeft
[] ボタンをクリックして、リセット ボタンが表示されるようにします。
変更は、次のようになります。
import React from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { StyleSheet, Text, View } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { ResetButton } from './ResetButton'; import { ItemListView } from './ItemListView'; import { OfflineModeButton } from './OfflineModeButton'; const Stack = createStackNavigator(); const headerRight = () => { return <OfflineModeButton />; }; const headerLeft = () => { return <ResetButton />; }; export const App = () => { return ( <> {/* All screens nested in RealmProvider have access to the configured realm's hooks. */} <SafeAreaProvider> <NavigationContainer> <Stack.Navigator> <Stack.Screen name="Your To-Do List" component={ItemListView} options={{ headerTitleAlign: 'center', headerLeft, //headerRight, }} /> </Stack.Navigator> </NavigationContainer> <View style={styles.footer}> <Text style={styles.footerText}> Log in with the same account on another device or simulator to see your list sync in real time. </Text> </View> </SafeAreaProvider> </> ); }; const styles = StyleSheet.create({ footerText: { fontSize: 12, textAlign: 'center', marginVertical: 4, }, hyperlink: { color: 'blue', }, footer: { paddingHorizontal: 24, paddingVertical: 12, }, });
変更の実行と確認
最後に、react-native-dotenv
ライブラリでは、 React Nativeサーバーがキャッシュのクリア でリセットされる必要があります。これは、Bagel に機能を追加する場合では通常のことです。
そのためには、現在実行中のReact Nativeインスタンスを ctrl-c
でダウンさせ、次のコマンドを入力して、キャッシュがクリアされたインスタンスを実行します。
npm start -- --reset-cache
これで、Atlas データをReact Nativeアプリケーションに同期する準備がすべて完了しました。
ここで、アプリケーションをリセットします。 以前にアプリケーションのローカルデータベースに変更を加えた場合は、Atlas に保存されている内容の内容でローカルデータベースをリセットするために、新しい Reset
ボタンをクリックする必要があります。
これで、mockUserId
のすべての Todo リスト アイテムが表示されます。

問題が発生した場合は、エミュレータまたはシミュレーター内のアプリケーションを削除し、最初から起動するように再ビルドしてください。
まだ問題が発生している場合は、例リポジトリの03 -Sync-Data-From-Atlas ブランチでその点までに行われた変更を表示できます。
フェーズ 5: バックエンドAPIの実装
検査コネクター
データが モバイルアプリケーションに同期されたら、次の手順では、ローカル変更を Atlas に伝達する方法を作成します。
このフェーズでは、次の操作を行います。
Connector
にuploadData
メソッドを実装しますモバイルデバイスからの操作を処理するための簡単なバックエンドサーバーを作成
簡単にするために、このガイドではサーバーをローカルで実行します。 本番環境のユースケースでは、これらのリクエストを処理するためにクラウドサービス(例: AzureApps は、これをサポートするサーバーレスクラウド機能を提供します)。
アップロード メソッドを実装する
まず、モバイルアプリケーションでローカル変更が行われたときに uploadData
メソッドに送信される操作を確認します。
source/PowerSync.ts
に次の変更を加えます。
async uploadData(database: AbstractPowerSyncDatabase) { const batch = await database.getCrudBatch(); console.log('batch', JSON.stringify(batch, null, 2)); }
次に、モバイルアプリケーションで次のような変更を加えます。
アイテムの削除
アイテムを完了と不完全の切り替え
新しい項目の追加
uploadData
メソッドの実装を完了して、取得リクエストでこの情報を送信します。
まず、.env
に新しい値を追加します。
BACKEND_ENDPOINT=http://localhost:8000
および types/env.d.ts
:
declare module '@env' { export const AUTH_TOKEN: string; export const POWERSYNC_ENDPOINT: string; export const BACKEND_ENDPOINT: string; }
Android エミュレータを使用している場合は、ポート 8000
上の localhost
へのリクエストがエミュレータからローカル マシンに転送されることを確認する必要があります。 これを有効にするには、次のコマンドを実行します。
adb reverse tcp:8000 tcp:8000
次に、source/PowerSync.ts
のインポート ステートメントに BACKEND_ENDPOINT
を追加します。
import { AUTH_TOKEN, POWERSYNC_ENDPOINT, BACKEND_ENDPOINT } from '@env';
次に、uploadData
メソッドを更新します。
async uploadData(database: AbstractPowerSyncDatabase) { const batch = await database.getCrudBatch(); if (batch === null) { return; } const result = await fetch(`${BACKEND_ENDPOINT}/update`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(batch.crud), }); if (!result.ok) { throw new Error('Failed to upload data'); } batch.complete(); }
更新されたメソッドは、 CRUD操作の配列をバックエンドエンドポイントに送信するようになりました。
アプリケーションがオフラインの場合、失敗します。
アプリケーションが正の応答を受け取った場合、操作は完了としてマークされ、操作のバッチするはモバイルアプリケーションから削除されます。
バックエンド サーバーの作成
ここで、プロジェクトに backend
という新しいフォルダを作成します。
mkdir backend
次に、package.json
ファイルを作成します。
{ "main": "index.js", "scripts": { "start": "node --env-file=.env index.js" }, "dependencies": { "express": "^4.21.2", "mongodb": "^6.12.0" } }
この package.json
には、.env
の変数をサービスに追加する start
スクリプトが含まれています。
以前のバージョンの Atlas connection string
を使用して新しい .env
を作成します。
MONGODB_URI=<connection_string>
ここで、依存関係をインストールします。
npm install
このガイドには、このサービスにTypescriptやその他のツールを追加する方法は含まれていませんが、無料で追加できることに注意してください。 さらに、このガイドでは検証は最小限に抑え、 MongoDBに挿入されるモバイルアプリケーションからのデータを準備するために必要な変更のみを実装します。
まず、次の内容で index.js
を作成します。
const express = require("express"); const { MongoClient, ObjectId } = require("mongodb"); const app = express(); app.use(express.json()); // MongoDB setup const client = new MongoClient( process.env.MONGODB_URI || "mongodb://localhost:27017", ); // Helper function to coerce isComplete to boolean function coerceItemData(data) { if (data && "isComplete" in data) { data.isComplete = !!Number(data.isComplete); } return data; } async function start() { await client.connect(); const db = client.db("PowerSync"); const items = db.collection("Item"); app.post("/update", async (req, res) => { const operations = req.body; try { for (const op of operations) { console.log(JSON.stringify(op, null, 2)); switch (op.op) { case "PUT": await items.insertOne({ ...coerceItemData(op.data), _id: new ObjectId(op.id), }); break; case "PATCH": await items.updateOne( { _id: new ObjectId(op.id) }, { $set: coerceItemData(op.data) }, ); break; case "DELETE": await items.deleteOne({ _id: new ObjectId(op.id), }); break; } } res.json({ success: true }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.listen(8000, () => { console.log("Server running on port 8000"); }); } start().catch(console.error);
上記のサービスでは、isComplete
が boolean
値に強制されていることに注意してください。 これにより、新しいトドリスト アイテムは、1
または0
ではなく、true
またはfalse
でMongoDBに到達します。 ObjectId
インスタンスも op.id
から作成されています。 これを _id
プロパティに設定すると、データはMongoDB の要件とベストプラクティスに準拠して作成されます。
変更の実行と確認
これで、サーバーを起動できます。
npm start
モバイルアプリケーションはすでにこのエンドポイントに操作を送信しようとしているはずです。 console.log
ステートメントにはリクエストが送信され、変更が Atlas に反映されるよう表示されます。
これは、 Atlas UIまたはMongoDB CompassでMongoDBコレクションを表示することで確認できます。

これで、Atlas との間でデータを同期する完全に機能するモバイルアプリケーションが完成しました。 また、Wifi をオフにして、アプリがオフラインのときにどのように機能するかをテストすることもできます。
問題が発生した場合は、例リポジトリの04 -Write-Backend ブランチでその点までに行われた変更を表示できます。
フェーズ 6: 最後の処理とクリーンアップ
この最終フェーズでは、2 つのオプションのアプリテスト機能を実装する方法と、プロジェクトの不要なコードと依存関係をクリーンアップする方法について説明します。
このアプリケーション を作成するプロセスでは、次の機能は省略されました。 すべてのタスクを表示 と オフラインモードに切り替えます。これらの機能はアプリの機能をテストするのに役立ちますが、本番アプリケーションでの使用を意図したものではありません。
注意
これらの機能に関連する手順は、任意としてマークされています。 不要な場合は、これらの任意の手順をスキップしてください。
すべて表示トグルを実装する(任意)
オプションの [すべて表示] トグルを実装するために、クライアントパラメーター に基づいてアクティブ化される 2 つ目のバケットが作成されます。これを適用するには、現在の同期セッションを切断し、新しい値セットで再接続します。 この値は view_all
と呼ばれるブール値になり、クラスターで作成されたすべての Todo リスト アイテムを表示するための安全でないバックグラウンドとして使用されます。 この機能は、特定のパラメーターに基づいてバケットが動的に作成されることを紹介するのに役立ちます。
注意
ここで使用されている方法は安全でないため、これを実行するにはバケットで accept_potentially_dangerous_queries
フラグを有効にする必要があります。 これを安全に実現する方法は、ユーザー ロールに基づいてバッキングデータベース内のユーザーの認可を更新することです。これは、このガイドの範囲外です。
開始するには、PowerSync ダッシュボードに移動し、同期ルールを更新して、view_all
パラメータが設定されているバケットを含めます。
bucket_definitions: user_buckets: parameters: - SELECT request.user_id() as user_id data: - SELECT _id as id FROM "Item" WHERE bucket.user_id = 'global' OR owner_id = bucket.user_id view_all_bucket: accept_potentially_dangerous_queries: true parameters: - SELECT (request.parameters() ->> 'view_all') as view_all data: - SELECT _id as id FROM "Item" WHERE bucket.view_all = true
バケット定義はまとめられているため、view_all_bucket
がアクティブな場合は user_buckets
データに追加されることに注意してください。
次に、プロジェクト内の source/PowerSync.ts
を更新して、view_all
フラグの状態を決定するためのローカル変数を含め、それを 接続インスタンスのパラメーターに適用します。
まず、viewAll
パラメータを追加し、setupPowerSync
関数を更新します。
let viewAll = false; export const setupPowerSync = (): PowerSyncDatabase => { const connector = new Connector(); powerSync.connect(connector, {params: {view_all: viewAll}}); return powerSync; };
次に、次の 2 つの関数を追加します。
export const resetPowerSync = async () => { await powerSync.disconnectAndClear(); setupPowerSync(); }; export const toggleViewAll = () => { viewAll = !viewAll; resetPowerSync(); };
最後に、source/ItemListView.tsx
を更新します。
まず、PowerSync
から toggleViewAll
をインポートします。
import { toggleViewAll } from './PowerSync';
次に、[すべてのタスクを表示] スイッチの onValueChange
属性を変更して、toggleViewAll
メソッドを呼び出します。 次のコードを使用して、Text
コンポーネントと Switch
コンポーネントを置き換えます。
<Text style={styles.toggleText}>Show All Tasks</Text> <Switch trackColor={{true: '#00ED64'}} onValueChange={() => { setShowAllItems(!showAllItems); toggleViewAll(); }} value={showAllItems} />
ここでアプリケーションを再起動し、アプリが意図したとおりに動作することを確認します。

オフラインモードの切り替えを実装する(任意)
オプションの オフライン モード トグルを実装するには、同期セッションを切断 して再接続する必要があります。 これにより、同期に接続されていないときにローカルな変更を加え、同期セッションが再確立されたときにローカルで変更が送信されたことを確認できます。
接続状態の変数を追加し、これを切り替えるためのメソッドを作成して、PowerSyncクライアントで connect
メソッドと disconnect
メソッドを呼び出します。
まず、以下を source/PowerSync.ts
に追加します。
let connection = true; export const toggleConnection = () => { if (connection) { powerSync.disconnect(); } else { setupPowerSync(); } connection = !connection; };
次に、source/OfflineModeButton.tsx
をリファクタリングしてRealm機能を削除し、新しい toggleConnection
メソッドを呼び出して置き換えます。 また、いくつかのインポートを追加する必要があります。
import { useState } from 'react'; import { Pressable, Text, StyleSheet } from 'react-native'; import { colors } from './Colors'; import {toggleConnection} from './PowerSync'; export function OfflineModeButton() { const [pauseSync, togglePauseSync] = useState(false); return ( <Pressable onPress={() => { toggleConnection(); togglePauseSync(!pauseSync); }}> <Text style={styles.buttonText}> {pauseSync ? 'Enable Sync' : 'Disable Sync'} </Text> </Pressable> ); } const styles = StyleSheet.create({ buttonText: { padding: 12, color: colors.primary, }, });
最後に、source/App.tsx
を開き、headerRight
コンポーネントのコメントを解除してアプリケーションの Stack.Screen
に戻します。
<Stack.Screen name="Your To-Do List" component={ItemListView} options={{ headerTitleAlign: 'center', headerLeft, headerRight, }} />
ここで、アプリの 2 つ目のインスタンスを開き、いくつかの変更を加えて、更新を確認します。

クリーンアップ プロジェクト
最後に、プロジェクトをクリーンアップします 。
次のファイルは安全に削除できます。
atlasConfig.json
source/WelcomeView.tsx
次の依存関係を package.json
から削除することもできます。
@realm/react
realm
結果と次のステップ
このガイドでは、 PowerSync への移行を開始するためのビルド ブロックについて説明しています。
要約するには、 このガイドに従うことで、次のことが実現されるはずです。
サンプルデータを使用してMongoDBデータベースを配置しました
サンプルデータを同期する PowerSync サービスを配置しました
診断ツールを使用してこのデータを表示およびクエリする方法を学びます
Device Syncモバイルアプリケーションをローカルのみに変換しました
ローカル専用のRealmデータベースから PowerSync に移行しました
PowerSync からモバイルデータベースへの同期を設定する
PowerSyncクライアントからの変更をMongoDBにプッシュするためのバックエンドを作成しました
次の手順では、モバイルアプリケーションの小さな部分を取得し、PowerSync を使用するように変換してみてください。 より高度なユースケースに関する将来のドキュメントにも注意してください。