A partir del 30 de septiembre de 2025, Atlas App Services alcanzó el final de su vida útil y MongoDB ya no lo respalda activamente.
PowerSync es una excelente alternativa a Atlas Device Sync. Es una solución basada en SQLite y podría ser la solución ideal para migrar si tiene una aplicación móvil que utiliza Device Sync.
Este tutorial te guiará por los pasos necesarios para migrar una aplicación móvil de Device Sync, escrita en React Native, a PowerSync. Tus datos de backend permanecerán en Atlas, por lo que deberás configurar el servicio PowerSync, actualizar los esquemas y enlaces de la base de datos local, y configurar un servicio de backend para escribir en Atlas.
Este tutorial utiliza una aplicación de lista de tareas pendientes de Realm para React Native disponible en repositorio de ejemplo de realm2powersync.
Fase 1: Preparación y configuración
Implementar un clúster Atlas
Primero, debe implementar un clúster Atlas e ingresar datos de prueba. Esto le guiará como si estuviera configurando Atlas por primera vez. Si ya tiene un clúster implementado, puede omitir este paso.
Navegue a MongoDB Atlas y regístrese para obtener una cuenta Atlas, o inicie sesión si ya tiene una cuenta.
A continuación, cree un clúster.
![Captura de pantalla de la interfaz de usuario]()
Para realizar pruebas, seleccione el clúster M0 (libre) con la configuración predeterminada. Puede realizar cambios adicionales según sus necesidades.
Haz clic en Crear implementación.
![Captura de pantalla de la interfaz de usuario]()
Regresarás a tu panel de control. El modal "Conectar al clúster" se mostrará automáticamente.
Haga clic en Elegir un método de conexión y luego seleccione Controladores.
![Captura de pantalla de la interfaz de usuario]()
Desde esta pantalla, copia la URL que se muestra en el paso 3.
![Captura de pantalla de la interfaz de usuario]()
Añade tu cadena de conexión al código de tu aplicación. Esta es tu cadena de conexión; es necesaria para acceder a tu instancia de MongoDB. Guarda la cadena de conexión para futuras consultas.
En los próximos pasos, creará un nombre de usuario y una contraseña que la instancia de PowerSync utilizará para conectarse a la base de datos.
Haga clic en Listo para cerrar el modal.
Una vez que el clúster termine de implementarse, su panel de control debería verse similar al siguiente.
Haga clic en Agregar datos para crear una nueva base de datos.
![Captura de pantalla de la interfaz de usuario]()
Desde la tarjeta Crear una base de datos en Atlas, haga clic en INICIAR.
![Captura de pantalla de la interfaz de usuario]()
Cree una base de datos llamada PowerSync y una colección llamada Item, luego haga clic en Crear base de datos.
![Captura de pantalla de la interfaz de usuario]()
Regresará al panel de control y debería ver la base de datos y la colección recién creadas:
![Captura de pantalla de la interfaz de usuario]()
Por último, debes crear un nuevo usuario que PowerSync utilizará para conectarse a esta base de datos.
En la barra lateral izquierda, haga clic en Acceso a la base de datos bajo el encabezado Seguridad.
![Captura de pantalla de la interfaz de usuario]()
Haga clic en Agregar nuevo usuario de base de datos y cree un nuevo usuario llamado
powersyncy proporcione una contraseña. Anote el nombre de usuario y la contraseña que usará en la cadena de conexión que copió anteriormente.Nota
Si su nombre de usuario o contraseña contiene alguno de los siguientes caracteres especiales, debe convertirlos a un formato compatible con URL para su cadena de conexión:,,,,,,,,.
$:/?!#[]@Puede hacerlo manualmente o usar una aplicación de codificación de URL, como urlencoder.org.En la sección Privilegios de usuario de la base de datos, haga clic en Agregar privilegio específico y agregue un privilegio para un rol
readWritey undbAdminrol para la base de datos PowerSync.![Captura de pantalla de la interfaz de usuario]()
Haga clic en Agregar usuario.
Debería ver el usuario recién creado con los permisos de base de datos necesarios.
![Captura de pantalla de la interfaz de usuario]()
Para obtener más detalles sobre los permisos de usuario, consulte la sección MongoDB de la guía de configuración de la base de datos de origen de PowerSync.
Agregar IP de PowerSync a la lista de acceso de IP
Para que PowerSync acceda a la base de datos que se ejecuta en Atlas, debe agregar las direcciones IP del servicio a la lista de acceso IP. Estas direcciones IP se encuentran en la documentación de Seguridad y Filtrado de IP de PowerSync.
En la barra lateral izquierda, haga clic en Acceso a la red debajo del encabezado Seguridad.
Haga clic en + Agregar dirección IP e introdúzcala. Para facilitar la administración de esta lista en el futuro, recomendamos también incluir PowerSync como comentario opcional.
Haga clic en Confirmar y repita este proceso para cada IP.
Importar datos de muestra
Si aún no lo ha hecho, actualice los marcadores de posición en la cadena de conexión que copió anteriormente con el nombre de usuario y la contraseña de su usuario de base de datos.
En este paso, importará algunos datos de muestra que se utilizarán para sincronizar datos en pasos futuros.
Primero, instala MongoDB Database Tools para acceder a mongoimport. Consulta las instrucciones de la guía de instalación para tu sistema operativo.
Después de haber instalado database-tools, ingrese lo siguiente en la terminal para confirmar que puede acceder a mongoimport:
mongoimport --version
Esto debería devolver la versión de la herramienta. Si tiene problemas, consulte la guía de instalación anterior.
A continuación, cree un archivo JSON llamado sample.json con el siguiente contenido:
[ { "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" } ]
Estos datos de ejemplo contienen algunos elementos de la lista de tareas pendientes. El owner_id se usará para filtrar ejemplos más adelante en este tutorial.
Para importar este JSON, ingrese el siguiente comando, reemplazando el marcador de posición <connection-string> con su cadena de conexión:
mongoimport --uri="<connection-string>" --db=PowerSync --collection=Item --file=sample.json --jsonArray
Deberías ver el siguiente mensaje:
10 document(s) imported successfully. 0 document(s) failed to import.
De lo contrario, confirme que los parámetros de comando (incluida la cadena de conexión) sean correctos y que su usuario Atlas tenga el acceso correcto a la base de datos.
Puede ver y administrar los documentos insertados accediendo a su colección en la interfaz de usuario de Atlas o usando la aplicación de escritorio MongoDB Compass. Para ver y administrar su base de datos y colecciones a través de MongoDB Compass, debe conectarse usando la misma cadena de conexión.

Configurar PowerSync
Ahora navegue a PowerSync y regístrese o inicie sesión.
Si está iniciando sesión por primera vez, deberá crear una nueva instancia para comenzar.
Crea una nueva instancia llamada TodoList.

Seleccione MongoDB como la base de datos de conexión.

Utilice la cadena de conexión Atlas para completar la configuración de conexión.
Importante
Utilice una versión abreviada de su cadena de conexión que no contenga su nombre de usuario, contraseña ni otros parámetros de URL. Por ejemplo, su conexión se verá así: mongodb+srv://m0cluster.h6folge.mongodb.net/.
Ingrese el nombre de la base de datos ("PowerSync"), el nombre de usuario ("powersync") y la contraseña que asignó a esta cuenta en un paso anterior.

Haga clic en Probar conexión para asegurarse de que puede conectarse correctamente.
Si ve el siguiente error, confirme que todas las IP del servicio PowerSync requeridas estén en su lista de acceso de IP de Atlas.

Si aún tiene problemas, consulte la Guía de conexión de base de datos PowerSync para conexiones MongoDB.
Haga clic en Siguiente para implementar su nueva instancia de PowerSync. Este proceso puede tardar unos minutos.
Una vez implementada su instancia, puede asegurarse de poder ver los datos migrados creando algunas reglas de sincronización básicas.
Primero, elimine las reglas de sincronización predeterminadas y reemplácelas con las siguientes:
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
Para que los elementos se sincronicen correctamente con el servicio PowerSync, tenga en cuenta lo siguiente:
El
_iddebe asignarse aid.El nombre de la colección "Item" debe estar entre comillas. Esto se debe a que el nombre de nuestra colección empieza con mayúscula.
Los buckets específicos del usuario deben coincidir con un
user_iddeglobal, lo que proporciona acceso a toda la base de datos. De lo contrario, coincidirá con eluser_idproporcionado, que se recuperará del token de autenticación.
Tenga en cuenta que las reglas de sincronización de PowerSync son un tema bastante complejo. Para obtener más información, puede consultar esta entrada del blog sobre reglas de sincronización o la documentación de las reglas de sincronización de PowerSync.
Haga clic en Guardar e implementar. La implementación puede tardar varios minutos en completarse.
Una vez completada la implementación, debería ver lo siguiente:

Una vez completada la implementación, debería ver el estado apropiado.
Si recibe algún error, asegúrese de que el PowerSync usuario esté configurado con los permisos enumerados en la documentación de configuración de la base de datos de origen de PowerSync.
Haga clic en Administrar instancias para revisar las reglas de sincronización y el estado de implementación.
Ver datos sincronizados
Para finalizar esta configuración, usará la aplicación PowerSync Diagnostics para ver los elementos de la lista de tareas pendientes que acaba de crear y agregar a sus reglas de sincronización. Para usar esta herramienta, primero debe crear un token de desarrollo.
En la parte superior de la página PowerSync, haga clic en
Manage Instances.En la barra lateral izquierda, haz clic en puntos suspensivos (…) junto a ListaDeTareas para abrir el menú contextual de esta instancia y luego selecciona Editar Instancia.
Seleccione la pestaña Autenticación de cliente y haga clic en Habilitar tokens de desarrollo.
Haga clic en Guardar e implementar.

Haga clic en los puntos suspensivos (…) junto a TodoList para abrir nuevamente el menú contextual de esta instancia y seleccione Generar token de desarrollo.
Se le solicitará que proporcione un token subject/user_id. Este actuará como user_id y podrá configurar sus reglas de sincronización para que actúen en consecuencia.
Con las reglas de sincronización que definimos anteriormente, puedes establecer el subject/user_id en global para generar un token que tendrá acceso a todo el conjunto de datos. También puedes establecerlo en mockUserId o mockUserId2 para sincronizar con un owner_id específico.
Copie el token generado, luego abra la aplicación de diagnóstico y péguelo en el token de desarrollo.
Nota
El token de desarrollo caducará en 12 horas. La herramienta de diagnóstico dejará de sincronizarse con Atlas después de su vencimiento, por lo que deberá generar un nuevo token si desea que se reanude la sincronización.
Deberías ver una página similar a ésta.

En la barra lateral izquierda,haga clic en Consola SQL.
Crea una consulta SELECT para ver todos los elementos:
SELECT * FROM Item

Ahora tienes todos los servicios necesarios para sincronizar tu base de datos MongoDB con una aplicación móvil.
Fase 2: Preparar la aplicación Realm para la migración
Clonar proyecto inicial
En esta fase, clonarás una aplicación de lista de tareas pendientes de Realm para React Native. La main rama del repositorio de ejemplo contiene el resultado final de la migración.
Para seguir esta guía usando el repositorio de ejemplo, consulte la rama 00-Start-Here:
git clone https://github.com/takameyer/realm2powersync cd realm2powersync git checkout 00-Start-Here
A continuación, instale las dependencias para que el editor pueda recoger cualquier importación y garantizar que no haya errores mientras edita este proyecto.
Importante
Este tutorial asume que tienes instalada la última versión de Node.js.
npm install
Dado que la aplicación asume que hay un clúster Atlas con un servicio Device Sync activo, aún no se puede ejecutar. En los siguientes pasos, realizará las modificaciones necesarias para ejecutar el proyecto como una aplicación local.
Refactorizar Device Sync Proyecto a solo local
Debe eliminar las partes de Atlas Device Sync para que la aplicación se ejecute solo con datos locales.
Primero, abra source/AppWrapper.txs y elimine la configuración AppProvider, UserProvider y sync.
El archivo AppWrapper.txs actualizado debería parecerse al siguiente:
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, }, });
A continuación, abra source/App.tsx y elimine las partes sobre dataExplorerLink y los botones de encabezado para OfflineMode y Logout (esto se implementará más adelante).
El archivo App.tsx actualizado debería parecerse al siguiente:
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, }, });
Finalmente, abra source/ItemListView.tsx y realice las siguientes actualizaciones:
Remover el código de suscripción de sincronización flexible
Reemplazar usuario con un usuario simulado: -
const user={ id: 'mockUserId' };Eliminar cualquier referencia
dataExplorererEliminar la funcionalidad del interruptor
Show All Tasks(esto se implementará más adelante)
El archivo ItemListView.tsx actualizado debería parecerse al siguiente:
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, }, });
Con estos cambios, la aplicación debería funcionar con una base de datos local.
Ejecutar y verificar cambios
Antes de comenzar la migración, debe compilar y ejecutar la aplicación actualizada para verificar que funcione según lo previsto.
Para iOS, ejecute los siguientes comandos:
npx pod-install npm run ios
Para Android, ejecute el siguiente comando:
npm run android
Tenga en cuenta que los errores de compilación quedan fuera del alcance de esta documentación. Si experimenta problemas relacionados con la compilación, consulte la documentación de React Native para asegurarse de que su entorno esté configurado correctamente.
Mientras tu aplicación se ejecuta, puedes verificar su funcionalidad básica. Deberías poder:
Crear nuevos elementos
Marcar elementos como terminados
Eliminar elementos

Fase 3: Migrar de Realm al cliente PowerSync
Instalar dependencias
Ahora que tiene una aplicación Realm solo local ejecutándose, puede comenzar a convertir esta aplicación para usar una versión solo local del cliente PowerSync.
PowerSync utiliza una base de datos basada en SQLite, por lo que deberá realizar algunas modificaciones en el esquema para garantizar que sea compatible.
Para lograr esto, deberá configurar el cliente PowerSync. Para obtener instrucciones detalladas, puede consultar el repositorio npm @powersync/react-native o la documentación de configuración de PowerSync para React Native.
Primero, ejecute el siguiente comando para agregar dependencias para PowerSync React Native Client, la base de datos SQLite de respaldo, un iterador asincrónico polyfill (requerido según las instrucciones), así como la dependencia bson (usada para generar ObjectId``s para insertar documentos en MongoDB):
npm install @powersync/react-native @journeyapps/react-native-quick-sqlite @azure/core-asynciterator-polyfill bson
Para configurar el polyfill, abre index.js y añade import '@azure/core-asynciterator-polyfill'; en la parte superior del archivo.
El archivo index.js actualizado debería parecerse al siguiente:
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);
Ahora que se agregaron las dependencias, debe reconstruir la aplicación:
Para iOS, ejecute
pod-install.Para Android, actualice el SDK mínimo requerido a 24 para que sea compatible con
react-native-quick-sqlite. Para ello, abraandroid/build.gradley cambieminSdkVersionde 21 a 24.
Migrar esquema de datos
Ahora configurará los tipos de datos y esquemas para la base de datos local.
Consulte la documentación de asignación de tipos de PowerSync MongoDB para determinar cómo configurar su esquema específico. A continuación, se presenta una breve referencia de los tipos disponibles:
Tipo | Descripción |
|---|---|
nulo | valores indefinidos o no establecidos |
entero | un entero con signo de 64bits |
real | un número de coma flotante de 64bits |
text | una cadena de texto UTF-8 |
blob | datos binarios |
Para este tutorial, modificarás source/ItemSchema.tsx de la siguiente manera:
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'];
Importante
El nombre de la propiedad pasado a Schema representa el nombre de la tabla local y la colección de MongoDB. En este caso, asegúrese de que se llame Item.
Tenga en cuenta que este código exporta los tipos directamente desde AppSchema, en lugar de tener que definirlos manualmente.
Refactorizar el código de la aplicación
Para acceder a PowerSync y vincular sus datos, necesitará acceder a los enlaces y proveedores del cliente PowerSync. Esta funcionalidad se proporciona mediante el componente PowerSyncContext.
Primero, actualice source/AppWrapper.tsx para usar PowerSyncContext e inicialice su cliente 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> ); };
A continuación, actualice ItemListView.tsx para usar el cliente PowerSync. Para ello, debe actualizar los enlaces utilizados en la parte superior de este componente:
Para obtener acceso a la base de datos local para realizar escrituras y actualizaciones, utilice el gancho
usePowerSync.Para obtener una lista de elementos de la lista de tareas pendientes que se vuelven a representar automáticamente al actualizar, utilice el gancho
useQuery.
Realice los siguientes cambios:
Remover
import { BSON } from 'realm';Add
import { ObjectId } from 'bson';Cambie las dos primeras líneas de la función
ItemListViewpara que coincidan con lo siguiente:export function ItemListView() { const db = usePowerSync(); const {data: items} = useQuery<Item>('SELECT * FROM Item');
A continuación, debe actualizar los métodos createItem, deleteItem y toggleItemIsComplete.
Para cada uno de estos métodos, utilizará el objeto db devuelto por usePowerSync. Al igual que con Realm, la base de datos local abre una transacción para realizar operaciones mutables, como insertar, actualizar o eliminar. También añadirá bloques try/catch para propagar cualquier error al frontend de la aplicación.
Tenga en cuenta que el código importa ObjectId desde bson para crear los identificadores únicos de cada elemento. Tenga en cuenta que PowerSync espera que los elementos de clave principal se llamen id.
El código de creación también implementa los valores por defecto de los elementos directamente en esta lógica. En este caso, isComplete se inicializa en falso y el id se inicializa con el resultado de string del ObjectId recién creado.
El método createItem se puede implementar de la siguiente manera:
// 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], );
Los métodos deleteItem y toggleItemIsComplete son similares, así que impleméntelos de la siguiente manera:
// 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], );
Finalmente, actualiza el FlatList renderizado. Harás lo siguiente:
Reemplazar instancias de
_idconidActualice el
keyExtractordelFlatListpara utilizar la cadenaiddirectamente.Anteriormente, la base de datos devolvía un
ObjectId. Esto deberá convertirse en una cadena.
El FlatList actualizado ahora se parece a lo siguiente:
<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> )} />
Ejecutar y verificar cambios
Una vez que hayas terminado de actualizar el código, deberías poder usar un PowerSync cliente local.
Para verificar, reconstruye la aplicación. Si usas iOS, no olvides actualizar los pods con npx pod-install.

Ahora deberías tener una aplicación funcional que te permite agregar, actualizar y borrar artículos de la lista de tareas usando PowerSync.
Si encuentra problemas, puede ver los cambios realizados hasta este punto en la rama 02-Migrate-Local-Client del repositorio de ejemplo.
Su aplicación móvil ahora está lista para sincronizar datos en tiempo real desde MongoDB.
Nota
Probablemente haya notado que los datos de Realm aún no se han migrado. Esta guía asume que el clúster de MongoDB alojado en Atlas es la fuente de datos y los sincroniza con la aplicación. La migración de datos locales queda fuera del alcance de este tutorial, pero podría abordarse en documentación futura.
Fase 4: Sincronizar datos de Atlas a PowerSync
Configuración inicial
Ahora debería tener un servicio PowerSync en ejecución que contiene datos sincronizados de Atlas, que se han verificado mediante la herramienta de diagnóstico PowerSync.
En esta fase, obtendrás estos datos para sincronizarlos en la aplicación React Native.
Para comenzar, debe crear una forma de establecer algunas variables de entorno para tokens y puntos finales.
Primero, instala react-native-dotenv en tus dependencias de desarrollo. Este es un plugin de Babel que toma un archivo .env de la raíz del proyecto y te permite importar variables de entorno directamente a la aplicación.
npm install -D react-native-dotenv
A continuación, agrega la siguiente línea al archivo babel.config.js:
module.exports = { presets: ['module:metro-react-native-babel-preset'], plugins: ['module:react-native-dotenv'], };
Crea un nuevo directorio llamado types y dentro de él crea un nuevo archivo llamado env.d.ts que contenga las siguientes variables que queremos importar:
declare module '@env' { export const AUTH_TOKEN: string; export const POWERSYNC_ENDPOINT: string; }
Debes recuperar los valores que necesitas para las variables de entorno desde PowerSync.
En la consola PowerSync, en la barra lateral izquierda, haga clic en … junto a TodoList para abrir el menú contextual.
Seleccione Editar instancia.
Copiar y guardar la URL.

A continuación, genere un nuevo token de desarrollo para su instancia con el subject/user_id. mockUserId Copie y guarde el token generado.
Desde tu proyecto de aplicación, crea un archivo .env en el directorio raíz y pega el endpoint y el token de PowerSync que acabas de generar:
POWERSYNC_ENDPOINT=<endpoint> AUTH_TOKEN=<dev-token>
Refactorizar el código del cliente
Necesitará refactorizar ligeramente su aplicación para que pueda conectarse a su instancia de PowerSync.
Primero, crea un nuevo archivo en source llamado PowerSync.ts y pega lo siguiente:
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(); };
Este archivo hace lo siguiente:
Crea una nueva clase
Connector, que se utilizará para configurar el token de desarrollo y el punto final de PowerSync en nuestro cliente PowerSync.Define una función
uploadDatasimulada, que se utilizará en la siguiente fase para enviar cambios a Atlas.Define métodos para configurar y restablecer nuestro cliente PowerSync. Restablecer el cliente será útil para el desarrollo ahora mismo, ya que cualquier cambio realizado se guardará en una cola. Hasta que se procesen estos cambios, no recibirás nuevas actualizaciones.
A continuación, actualice AppWrapper.tsx para utilizar el nuevo método 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> ); };
Luego, refactoriza LogoutButton.tsx para implementar el método resetPowerSync. Renómbralo ResetButton.tsx y actualiza su contenido como se indica a continuación:
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, }, });
Luego, modifique App.tsx para mostrar el botón Reset en el lado izquierdo del encabezado:
Reemplazar
import { LogoutButton } from './LogoutButton';porimport { ResetButton } from './ResetButton';En el
headerLeft, reemplace la línea existente conreturn <ResetButton />;Descomente la línea
//headerLeftpara que se muestre el botón Restablecer.
Sus cambios se verán así:
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, }, });
Ejecutar y verificar cambios
Por último, la biblioteca react-native-dotenv requiere que nuestro servidor React Native se restablezca con el caché borrado, lo cual es normal al agregar funcionalidad a Babel.
Para hacer esto, detén cualquier instancia de React Native que se esté ejecutando actualmente con ctrl-c, luego introduce lo siguiente para ejecutar la instancia con la caché borrada:
npm start -- --reset-cache
Ahora debería estar todo listo para sincronizar sus datos de Atlas en su aplicación React Native.
Ahora restablezca la aplicación. Si realizó modificaciones en la base de datos local de la aplicación anteriormente, deberá hacer clic en el botón Reset para restablecerla con el contenido almacenado en Atlas.
Ahora debería ver todos los elementos de la lista de tareas pendientes para mockUserId:

Si encuentra problemas, elimine la aplicación en su emulador/simulador y reconstrúyala para comenzar desde cero.
Si aún tiene problemas, puede ver los cambios realizados hasta este punto en la rama 03-Sync-Data-From-Atlas del repositorio de ejemplo.
Fase 5: Implementar la API de backend
Inspeccionar el conector
Ahora que sus datos se están sincronizando con la aplicación móvil, el siguiente paso es crear una forma de propagar los cambios locales a Atlas.
En esta fase usted:
Implementa el método
uploadDataen tuConnectorCrear un servidor backend simple para gestionar operaciones desde el dispositivo móvil
Para simplificar, esta guía ejecutará el servidor localmente. Para casos de producción, considere usar un servicio en la nube para gestionar estas solicitudes (por ejemplo, JourneyApps ofrece funciones en la nube sin servidor para facilitar este proceso).
Implementar el método de carga
Comience mirando las operaciones enviadas al método uploadData cuando se realizan cambios locales en la aplicación móvil.
Realice los siguientes cambios en source/PowerSync.ts:
async uploadData(database: AbstractPowerSyncDatabase) { const batch = await database.getCrudBatch(); console.log('batch', JSON.stringify(batch, null, 2)); }
A continuación, siguiente, realizará cambios en la aplicación móvil que incluirán:
Eliminar un elemento
Alternar un elemento como completo o incompleto
Agregar un nuevo elemento
Termine de implementar el método uploadData para enviar esta información en una solicitud de búsqueda.
Primero, agregue un nuevo valor a su .env:
BACKEND_ENDPOINT=http://localhost:8000
y types/env.d.ts:
declare module '@env' { export const AUTH_TOKEN: string; export const POWERSYNC_ENDPOINT: string; export const BACKEND_ENDPOINT: string; }
Si usa el emulador de Android, debe asegurarse de que las solicitudes a localhost en el puerto 8000 se reenvíen desde el emulador a su equipo local. Para habilitar esto, ejecute el siguiente comando:
adb reverse tcp:8000 tcp:8000
A continuación, agregue BACKEND_ENDPOINT a la declaración de importación en source/PowerSync.ts:
import { AUTH_TOKEN, POWERSYNC_ENDPOINT, BACKEND_ENDPOINT } from '@env';
Luego actualice el método 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(); }
El método actualizado ahora enviará una matriz de operaciones CRUD al punto final del backend:
Si la aplicación está fuera de línea, simplemente fallará.
Si la aplicación recibe una respuesta positiva, marcará las operaciones como completadas y el lote de operaciones se eliminará de la aplicación móvil.
Crear servidor backend
Ahora, crea una nueva carpeta en tu proyecto llamada backend:
mkdir backend
Luego, crea un archivo package.json:
{ "main": "index.js", "scripts": { "start": "node --env-file=.env index.js" }, "dependencies": { "express": "^4.21.2", "mongodb": "^6.12.0" } }
Este package.json incluye un script start que agrega variables de un .env al servicio.
Crea un nuevo .env con tu Atlas connection string de antes:
MONGODB_URI=<connection_string>
Ahora, instala las dependencias:
npm install
Tenga en cuenta que esta guía no explica cómo agregar TypeScript ni otras herramientas a este servicio, pero puede hacerlo libremente. Además, la guía minimiza la validación y solo implementa los cambios necesarios para preparar los datos provenientes de la aplicación móvil para su inserción en MongoDB.
Primero, crea un index.js con el siguiente contenido:
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);
Tenga en cuenta que, según el servicio anterior, el valor isComplete se convierte en boolean. Esto garantiza que los nuevos elementos de la lista de tareas pendientes lleguen a MongoDB con true o false en lugar de 1 o 0. También se crea una instancia ObjectId a partir de op.id. Al configurar esto con la propiedad _id, los datos se ajustarán a los requisitos y las mejores prácticas de MongoDB.
Ejecutar y verificar cambios
Ahora puedes poner en marcha el servidor:
npm start
La aplicación móvil ya debería estar intentando enviar operaciones a este punto final. La instrucción console.log debería mostrar las solicitudes a medida que se envían y los cambios deberían estar propagándose a Atlas.
Puede verificar esto viendo su colección de MongoDB en la interfaz de usuario de Atlas o en MongoDB Compass.

Ahora deberías tener una aplicación móvil completamente funcional que sincroniza datos con Atlas. También puedes intentar desactivar el wifi para comprobar cómo funciona la aplicación sin conexión.
Si encuentra problemas, puede ver los cambios realizados hasta este punto en la rama 04-Write-To-Backend del repositorio de ejemplo.
Fase 6: Toques finales y limpieza
Esta fase final cubre cómo implementar dos funciones opcionales de prueba de aplicaciones, así como también cómo limpiar su proyecto de cualquier código y dependencias innecesarias.
Durante la creación de esta aplicación, se omitieron las siguientes funciones: Mostrar todas las tareas y el interruptor de modo sin conexión. Estas funciones son útiles para probar la funcionalidad de la aplicación y no están diseñadas para usarse en una aplicación de producción.
Nota
Los pasos relacionados con estas funciones están marcados como opcionales. Si no le interesan, puede omitirlos.
Implementar el interruptor Mostrar todo (opcional)
Para implementar la opción "Mostrar todo", se creará un segundo bucket que se activará según un parámetro del cliente. Para ello, desconectará la sesión de sincronización actual y la volverá a conectar con un nuevo conjunto de valores. Este valor será un booleano view_all llamado, que se utilizará como puerta trasera insegura para mostrar todos los elementos de la lista de tareas pendientes creados en el clúster. Esta funcionalidad demuestra que los buckets se pueden crear dinámicamente según ciertos parámetros.
Nota
La metodología utilizada aquí es insegura, por lo que será necesario activar el indicador accept_potentially_dangerous_queries en el bucket para realizar esta operación. Una forma segura de lograrlo sería basarlo en un rol de usuario y actualizar las autorizaciones de los usuarios en su base de datos de respaldo, lo cual queda fuera del alcance de esta guía.
Para empezar, navega hasta el tablero de PowerSync y actualiza las reglas de sincronización para incluir un bucket según el parámetro view_all establecido:
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
Tenga en cuenta que las definiciones de bucket se combinan entre sí, por lo que cuando view_all_bucket está activo, se agregará a los datos user_buckets.
A continuación, actualiza source/PowerSync.ts en tu proyecto para incluir una variable local que determine el estado de la bandera view_all, y aplícala a los parámetros de la instancia de conexión.
Primero, agregue un parámetro viewAll y actualice la función setupPowerSync:
let viewAll = false; export const setupPowerSync = (): PowerSyncDatabase => { const connector = new Connector(); powerSync.connect(connector, {params: {view_all: viewAll}}); return powerSync; };
Luego, agrega las siguientes dos funciones:
export const resetPowerSync = async () => { await powerSync.disconnectAndClear(); setupPowerSync(); }; export const toggleViewAll = () => { viewAll = !viewAll; resetPowerSync(); };
Por último, actualice source/ItemListView.tsx.
Primero, importe toggleViewAll desde PowerSync:
import { toggleViewAll } from './PowerSync';
Luego, modifique el atributo onValueChange del parámetro "Mostrar todas las tareas" para invocar el método toggleViewAll. Use el siguiente código para reemplazar los componentes Text y Switch:
<Text style={styles.toggleText}>Show All Tasks</Text> <Switch trackColor={{true: '#00ED64'}} onValueChange={() => { setShowAllItems(!showAllItems); toggleViewAll(); }} value={showAllItems} />
Ahora reinicie su aplicación y verifique que funcione como se espera:

Implementar el modo sin conexión (opcional)
Para activar el modo sin conexión, deberá desconectar la sesión de sincronización y volver a conectarla. Esto le permitirá realizar cambios locales sin estar conectado a la sincronización y verificar que se envíen al restablecer la sesión.
Agregará una variable para el estado de la conexión, luego creará un método para alternarlo e invocará los métodos connect y disconnect en el cliente PowerSync.
Primero, agregue lo siguiente a source/PowerSync.ts:
let connection = true; export const toggleConnection = () => { if (connection) { powerSync.disconnect(); } else { setupPowerSync(); } connection = !connection; };
A continuación, refactorice el source/OfflineModeButton.tsx para eliminar la funcionalidad de Realm y reemplazarla invocando el nuevo método toggleConnection. También deberá agregar algunos import``s:
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, }, });
Por último, abra source/App.tsx y descomente el componente headerRight nuevamente en el Stack.Screen de la aplicación:
<Stack.Screen name="Your To-Do List" component={ItemListView} options={{ headerTitleAlign: 'center', headerLeft, headerRight, }} />
Ahora, verifique las actualizaciones abriendo una segunda instancia de la aplicación y luego realice algunos cambios:

Proyecto de limpieza
Finalmente puedes limpiar tu proyecto.
Los siguientes archivos se pueden eliminar de forma segura:
atlasConfig.jsonsource/WelcomeView.tsx
También puedes eliminar las siguientes dependencias de tu package.json:
@realm/reactrealm
Conclusión y siguientes pasos
Esta guía debería haberle proporcionado los elementos básicos para comenzar su viaje de migración a PowerSync.
En resumen, al seguir esta guía deberías haber logrado lo siguiente:
Se implementó una base de datos MongoDB con datos de muestra
Se implementó un servicio PowerSync que sincroniza los datos de muestra
Aprendí a ver y consultar estos datos usando la herramienta de diagnóstico.
Se convirtió una aplicación móvil de sincronización de dispositivos en solo local
Se migró de una base de datos de Realm solo local a PowerSync
Configurar la sincronización de PowerSync con una base de datos móvil
Se creó un backend para enviar cambios desde el cliente PowerSync a MongoDB
Para los próximos pasos, intente convertir una pequeña parte de su aplicación móvil para que use PowerSync. Esté atento a la documentación futura que aborda casos de uso más avanzados.










