Steps for building a simple mobile application using Expo SDK, Expo Router, and MongoDB Realm in React Native and plain Javascript.
Realm is a fast, scalable alternative to SQLite with mobile to cloud data sync that makes building real-time, reactive mobile apps easy.
I did this tutorial for those who want to build in javascript and not typescript but also to show my approach to using Realm with Expo Router's file-based approach to routing.
This is a companion blog post to go along with this video.
Getting Started
Create the application
npx create-expo-app@latest --template blank@sdk-49 app-with-realm
change into project directory
cd app-with-realm
Install additional libraries and packages for expo-router
npx expo install expo-router@latest react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar react-native-gesture-handler
Make modifications to support expo-router in package.json
{
"main": "expo-router/entry"
}
Not using web so skipping that part of documentation, but add the scheme app.json
"scheme": "app-with-realm",
update babel.config.js
to include the new plugin.
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ['expo-router/babel'],
};
};
Lets add the index page and then test it is working, remember the files all live in a new app directory that you have to create. /app/index.js
// /app/index.js
import { Text } from 'react-native';
export default function Page() {
return <Text>Home page</Text>;
}
Installing Realm
npm install realm @realm/react
to build locally run do the following, if not you will continue to get a pod install error
Error: Missing Realm constructor. Did you run "pod install"? Please see https://realm.io/docs/react-native/latest/#missing-realm-constructor for troubleshooting*
npx expo prebuild
then to run on ios, you will see the pod file get installed appropriately
npm run ios
Now we need to create our schema
will be using the one from the realm example but in plain javascript. Add file to app
directory
// app/Task.js
import Realm, { BSON } from "realm";
export class Task extends Realm.Object {
_id
description
isComplete
createdAt
static primaryKey = "_id";
static schema = {
name: "Task",
primaryKey: "_id",
properties: {
_id: 'uuid',
description: "string",
createdAt: {
type: "date",
default: new Date(),
},
isComplete: {
type: "bool",
default: false,
indexed: true,
},
},
};
constructor(realm, description) {
console.log("in constructor");
super(realm, {
_id: new BSON.UUID(),
description,
});
}
}
Now lets wrap our whole app with the provider by creating a layout at the app route. Add this file _layout.js
to the root of you app
directory.
We use the schema we created Task
as a parameter to the RealmProvider
// app/_layout.js
import 'react-native-get-random-values'
import { Stack} from "expo-router";
import { RealmProvider } from "@realm/react";
import { Task } from './Task';
export default function AppLayout() {
return (
<RealmProvider schema={[Task]}>
<Stack />
</RealmProvider>
);
}
update index.js
so that we can query our database using a useQuery
hook provided by realm.
// /app/index.js
import { Text, View } from "react-native";
import { useQuery } from "@realm/react";
import { Task } from "./Task";
export default function Page() {
const tasks = useQuery(Task);
console.log(tasks);
return (
<View>
<Text>TASK LIST</Text>
<Text>{JSON.stringify(tasks, null, 2)}</Text>
</View>
);
}
lets add some UI to add a Task
// /app/index.js
import {
Text,
TextInput,
View,
StyleSheet,
TouchableOpacity,
} from "react-native";
import { useQuery } from "@realm/react";
import { Task } from "./Task";
import { useRef } from "react";
export default function Page() {
// ref to hold description
const descriptionRef = useRef("");
// get the tasks
const tasks = useQuery(Task);
return (
<View style={{ height: Dimensions.get("screen").height - 132 }}>
<Text style={styles.title}>TASK LIST</Text>
{/* input for description */}
<TextInput
placeholder="Enter New Task"
autoCapitalize="none"
nativeID="description"
multiline={true}
numberOfLines={8}
value={descriptionRef.current}
onChangeText={(text) => {
descriptionRef.current = text;
}}
style={styles.textInput}
/>
{/* button to save the new task */}
<TouchableOpacity
style={styles.button}
onPress={() => {
createNewTask();
}}
>
<Text style={styles.buttonText}>SAVE TASK</Text>
</TouchableOpacity>
<Text>{JSON.stringify(tasks, null, 2)}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
margin: 16,
},
title: {
fontSize: 18,
margin: 16,
fontWeight: "700",
},
label: {
marginBottom: 8,
fontSize: 18,
fontWeight: "500",
// color: "#455fff",
},
textInput: {
fontSize: 20,
borderWidth: 1,
borderRadius: 4,
// borderColor: "#455fff",
paddingHorizontal: 8,
paddingVertical: 4,
marginBottom: 0,
marginHorizontal: 16,
},
button: {
backgroundColor: "grey",
padding: 10,
borderRadius: 5,
marginTop: 8,
marginLeft: 16,
width: 120,
},
buttonText: {
color: "white",
textAlign: "center",
fontWeight: "600",
fontSize: 12,
},
});
Now add the function, createNewTask
to save the task to the realm database. We will us the useRealm
hook in this function
import { useRealm } from "@realm/react";
then inside the component
const realm = useRealm();
Then in the component add the code for the createNewTask
function
const createNewTask = () => {
realm.write(() => {
const newTask = new Task(realm, descriptionRef.current);
// clear input field
descriptionRef.current = "";
// return task
return newTask;
});
};
Run the code and add a task
Lets add a component to render the tasks in a FlatList
import { useRealm } from "@realm/react";
import { StyleSheet, View, Text, Dimensions, Pressable } from "react-native";
import { FlatList } from "react-native-gesture-handler";
export const TaskList = ({ data }) => {
const realm = useRealm();
const renderItem = ({ item }) => (
<View style={styles.row}>
<View style={styles.item}>
<View style={{ display: "flex", flex: 12 }}>
<Text style={{ fontSize: 22, fontWeight: 'bold', marginBottom:8 }}>{item.description}</Text>
<Text style={{ fontSize: 18, marginBottom:4 }}>{item.createdAt.toString()}</Text>
<Text style={{ }}>{item._id + ""}</Text>
</View>
<View style={{ display: "flex", alignSelf: "center" }}>
<Pressable
onPress={() => onToggleStatus(item)}
style={[styles.status, item.isComplete && styles.completed]}
>
<Text style={[styles.icon]}>{item.isComplete ? "✓" : "○"}</Text>
</Pressable>
</View>
</View>
<Pressable onPress={()=>onDelete(item)} style={styles.deleteButton}>
<Text style={styles.deleteText}>Delete</Text>
</Pressable>
</View>
);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item._id + ""}
/>
);
};
const styles = StyleSheet.create({
item: {
display: "flex",
flexDirection: "row",
},
row: {
padding: 20,
borderBottomWidth: 1,
borderBottomColor: "#ccc",
width: Dimensions.get("screen").width,
},
icon: {
textAlign: "center",
fontSize: 20,
fontWeight: "bold",
textAlignVertical: "center",
},
status: {
width: 32,
height: 32,
},
deleteButton: {
backgroundColor: "red",
margin: 8,
marginLeft: 0,
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 8,
width: 100,
},
deleteText: {
textAlign: "center",
},
});
Add Toggle function
const onToggleStatus = (task) => {
console.log(task);
realm.write(() => {
task.isComplete = !task.isComplete;
});
};
Add delete Function
const onDelete = (task) => {
console.log(task);
realm.write(() => {
realm.delete(task);
});
};
Links
- Realm Database - https://realm.io/
- Realm React-Native - https://www.mongodb.com/docs/realm/sdk/react-native/install/
- Expo Router Doc - https://docs.expo.dev/routing/introduction/
Social Media
- Twitter - https://twitter.com/aaronksaunders
- Facebook - https://www.facebook.com/ClearlyInnovativeInc
- Instagram - https://www.instagram.com/aaronksaunders/
Top comments (7)
Thanks for the post! It looks very interesting and I’m going to save it for the weekend to really dig in. Seems like a great way to get my feet wet with Expo Router.
Hopefully you find it helpful, I also have a full series here on expo router - youtube.com/playlist?list=PL2PY2-9...
Great video! I've been really enjoying your Realm + React Native content.
I'm currently facing an issue, and I've been stuck on it for about four weeks now. Everything was smooth until I installed the Realm/React library and created the app layout. Suddenly, I encountered the error message: "Could not find the Realm Binary. Please consult our troubleshooting guide..." (Note that everything was working fine before creating the app layout.)
I've already tried the troubleshooting guide, but unfortunately, I'm still facing the same issue.
Just to provide some context, I'm working on a Windows machine with an Android device. Any suggestions or ideas on how to resolve this would be greatly appreciated!
According to the Realm documentation, you have to insall
expo-dev-client
to resolve this issuenpm install expo-dev-client
mongodb.com/docs/realm-sdks/js/lat...
Were you able to solve the problem? I am having the exact same problem. Windows machine with iOS device. Please advise.
Your article is genuinely incredible. Thanks for pointing out how to do the setup with JavaScript. I struggled with the realm docs and almost give up on some errors.
I also liked your way of defining the the schema objects and the creation for them.
Thanks a lot!
I am working on Windows and using expo go for android, I followed all of your instructions for setup and updated the expo from 48 to version 49, fixed the dependencies, and also prebuild the app still getting the pods error.
any suggestion to solve this.