A Practical Exercise of Atlas Device SDK for Web With Sync (Preview)
Rate this tutorial
Table of contents
- Atlas Device SDK for web with Device Sync and its real-world usage
- Architecture
- Basic components
- Building your own React web app
- Step 1: Setting up the back end
- Step 2: Creating an App Services app
- Step 3: Getting ready for Device Sync
- Step 4: Atlas Device SDK
- Step 5: Let the data flow (start using sync)!
- Implementation of the coffee app
- A comparison between Device Sync and the Web SDK without Sync
- What will our web app look like without Device Sync?
- Which one should we choose?
- Conclusions
- Appendix
The Device Sync feature of the Web SDK is a powerful tool designed to bring real-time data synchronization and automatic conflict resolution capabilities to cross-platform applications, seamlessly bridging the gap between users’ back ends and client-side data. It facilitates the creation of dynamic user experiences by ensuring eventual data consistency across client apps with syncing and conflict resolution.
In the real-world environment, certain client apps benefit from a high level of automation, therefore bringing users an intuitive interaction with the client app. For example, a coffee consumption counter web app that allows the user to keep track of the cups of coffee he/she consumes from different devices and dynamically calculates daily coffee intake will create a ubiquitous user experience.
In this tutorial, I will demonstrate how a developer can easily enable Device Sync to the above-mentioned coffee consumption web app. By the end of this article, you will be able to build a React web app that first syncs the cups of coffee you consumed during the day with MongoDB Atlas, then syncs the same data to different devices running the app. However, our journey doesn’t stop here. I will also showcase the difference between the aforementioned Web SDK with Sync (preview) and the Web SDK without automatic syncing when our app needs to sync data with the MongoDB back end. Hopefully, this article will also help you to make a choice between these two options.
In this tutorial, we will create two web apps with the same theme: a coffee consumption calculator. The web app that benefits from Device Sync will be named
Coffee app Device Sync
while the one following the traditional MongoDB client will be named Coffee app
.The coffee app with Device Sync utilizes Atlas Device Sync to synchronize data between the client app and backend server in real time whilst our coffee app without Device Sync relies on the MongoDB driver.
Data synchronization relies on the components below.
- App Services: App Services and its Atlas Device SDKs are a suite of development tools optimized for cross-platform devices such as mobile, IoT, and edge, to manage data via MongoDB’s edge database called Realm, which in turn leverages Device Sync. With various SDKs designed for different platforms, Realm enables the possibility of building data-driven applications for multiple mobile devices. The Web SDK we are going to explore in this article is one of the handy tools that help developers build intuitive web app experiences.
- User authentication: Before setting up Device Sync, we will need to authenticate a user. In our particular case, for the sake of simplicity, the
anonymous
method is being used. This procedure allows the sync from the client app to be associated with a specific user account on your backend App Services app. - Schema: You can see schema as the description of how your data looks, or a data model. Schema exists both in your client app’s code and App Services’ UI. You will need to provide the name of it within the configuration.
- Sync configuration: It is mandatory to provide the authenticated user, sync mode (flexible: true), and
initialSubscriptions
which defines a function that sets up initial subscriptions when Realm is opened. - Opening a synced realm: As you will see, we use
Realm.open(config);
to open a realm that is synchronized with Atlas. The whole process between your client app and back end, as you may have guessed, is bridged by Device Sync.
Once Realm is opened with the configuration we discussed above, any changes to the coffee objects in the local realm are automatically synchronized with Atlas. Likewise, changes made in Atlas are synchronized back to the local realm, keeping the data up-to-date across devices and the server. What’s even better is that the process of data synchronization happens seamlessly in the background without any user action involved.
Compared to the Device Sync feature, MongoDB also provides web apps with an alternative option to communicate with the back end. A connection directly to MongoDB services (a.k.a.
mongoClient
), as the option we will discuss further in this article, provides us with a semi-auto approach for performing CRUD operations.In a nutshell, using
mongoClient
to query data gives developers the freedom to initiate CRUD operations under designed situations. For example, you will only want the creation and deletion of data on your local realm to be synced to the back end when the user clicks on a certain button on your app, rather than automatically syncing changes to the server.To build a web application with Device Sync, we will need a few components to start with. I categorized them into two groups: the front end and the back end.
Front end:
- A React web app, as an interface that bridges the user and back end
Back end:
- MongoDB Atlas, as the cloud storage
- Data (in this article, we will use dummy data)
- MongoDB App Services app, as the web app’s business logic
These components briefly describe the building blocks of a web app powered by MongoDB App Services. The coffee app is just an example to showcase how Device Sync works and the possibilities for developers to build more complicated apps.
In this section, I will provide step-by-step instructions on how to build your own copy of Coffee App. By the end, you will be able to interact with Realm and Device Sync on your own.
MongoDB Atlas is used as the backend server of the web app. Essentially, Atlas has different tiers, from M0 to M700, which represent the difference in storage size, cloud server performance, and limitations from low to high. For more details on this topic, kindly refer to our documentation.
In this tutorial, we will use the free tier (M0), as it is just powerful enough for learning purposes.
Once the account is ready, we can proceed to “Create a Project.”
You can see “Project” as a container, which can hold multiple clusters and also the datasource of any additional services (e.g., App Services uses Atlas as a datasource).
The next step is to select the tier, cloud provider, and region.
Note: In production environments, M10 and above are recommended whilst you can configure even higher tiers by setting up advanced configuration options. The self-explanatory website will guide you through further steps, such as setting a username and password for your cluster. These will be the key part for you to access your cluster. After the auto-deployment process has finished, you will see your cluster/database showing up like below.
Note:
Cluster0
is the default name for a newly created cluster. You can give any name to your cluster during the creation process.We now have a fully functional cluster up and running. Please refer to our tutorial for more details on creating and configuring clusters as this will not be in the scope of this article.
App Services (previously named Realm) is a suite of cloud-based tools (i.e., serverless functions, Device Sync, user management, rules) designed to streamline app development with Atlas. In other words, Atlas works as the datasource for App Services.
Our coffee app will utilize App Services in such a way that the back end will provide data sync among client apps.
For this tutorial, we just need to create an empty app. You can easily do so by skipping any template recommendations.
Once your App Services app has been created, pay attention to the following items:
- App ID: This is an auto-generated unique parameter and is needed for referencing your app in different scenarios. For example, within the web app’s code, you will need to specify which app your code is working with. In the screenshot below, I defined
REALM_APP_ID
to be the App Services app I created.
- Schema: Schema defines how our data looks. In other words, we define a structure of the data we are going to store in the cluster, client devices with specific data types (string, long, etc.), and whether certain properties are
required
or optional.
Please note: There are also two ways to define schema:
- Method 1: Populating documents to Atlas and defining schema from the App Services UI
- Method 2: Defining
Object Schema
on your client app and using Development Mode to allow configuring data models and queryable fields from the client app
(We will use Method 2 for the example app. Select “Device Sync” on the left-hand side panel and switch on Development Mode. This will allow you to define schema within your client app’s code and then have it applied to the back end automatically.)
For example, let’s define the coffee app’s schema as shown below.
Alternatively, the App Services UI also provides an easier and more straightforward way to define schema. Select the “Table View” tab to switch between graphical and JSON editor views. Within the table view, App Services will automatically populate your data’s Field Name and let you configure your data in a graphical interface.
Our documentation gives a very good explanation of why schema is a mandatory and important component of Device Sync:
To use Atlas Device Sync, you must define your data model in two formats:
- App Services schema: This is a server-side schema that defines your data in BSON. Device Sync uses the App Services schema to convert your data to MongoDB documents, enforce validation, and synchronize data between client devices and Atlas.
- Realm object schema: This is client-side schema of data defined using the Realm SDKs. Each Realm SDK defines the Realm object schema in its own language-specific way. The Realm SDKs use this schema to store data in the Realm database and synchronize data with Device Sync.
Note: As you can see, Development Mode allows your client app to define a schema and have it automatically reflected server-side. (Simply speaking, schema on your server will be modified by the client app.)
As you probably already guessed, this has the potential to mess with your app’s schema and cause serious issues (i.e., stopping Device Sync) in the production environment.
We only use Development Mode for learning purposes and a development environment, hence the name.
By now, we have created an App Services app and configured it to be ready for our coffee app project.
We are now ready to implement Device Sync in the coffee app. Sync happens when the following requirements are satisfied.
- Client devices are connected to the network and have an established connection to the server.
- The client has data to sync with the server and it initiates a sync session.
- The client sends IDENT messages to the server. *You can see IDENT messages as an identifier that the client uses to tell the server exactly what Realm file it needs to sync and the status of the client realm (i.e., if the current version is the client realm’s most recently synced server version).
The roadmap below shows the workflow of a web app with the Device Sync feature.
Next up, let’s look at the basic components of Device Sync.
- SDK: A realm SDK is what you will use as a tool kit to work with synced realms.
- Data model: See the data model as a description of the data object you want to synchronize between a synced realm and your back end (Atlas). The models include the object type for each object and the structure of the object.
Under the hood, sync is made possible by translating and applying changes between the local realm and the back end. When using Flexible Sync, only the data you subscribe to will be synced.
3. Rules and roles: Rules and roles allow you to define which data (rules) to sync and each user’s ability to read and write data (roles).
To be more specific, rules as a JSON expression dynamically evaluate the input document and decide whether the input can be written/read to object data stored in the back end. Roles evaluate who has the right to perform certain actions to the data (i.e., read/write). They can both be defined within the App Services UI’s “Rules” section.
As an example app, you can configure a collection to be
read-only
as shown below.One thing worth mentioning specifically here is the data model.
Let’s look at the example of the data model from the coffee app we will be building.
The data model can be divided into two parts from a developer’s point of view: the client app data model and the server-side schema.
The screenshot above shows the document schema for the object we would like to sync, which resides within the back end. The schema is usually auto-generated by App Services. It reflects exactly how the object is structured and the data types of each field.
Accordingly, we will need the matching data model defined in our client application’s code.
There are two ways to define the matching object models: Development Mode and the App Services UI.
- Development Mode: This mode can be enabled within the App Services UI’s “Device Sync” section. What it does is allow you to define the data model first within your client app’s code. Then, App Services will adopt the same data model on the server side. This is a handy feature for developers to build the app and sync experience in a more intuitive way. Please note that this mode should be switched off when the app moves on to the production environment.
- App Services UI: Alternatively, the App Services UI provides a very useful tool to auto-generate the data model according to the document schema. Even better, you can select the specific programming language you are using for the client app, as shown below.
With the auto-generated data model code, App Services makes it possible for us to easily integrate this basic component into the client app.
Above is the situation where Device Sync will be triggered, alongside the minimal components we will need to make it work.
Despite the differences in programming languages and functionalities, SDKs share the following common points:
Despite the differences in programming languages and functionalities, SDKs share the following common points:
- Providing a core database API for creating and working with local databases
- Providing an API that you need to connect to an Atlas App Services server, and therefore, server-side features like Device Sync, functions, triggers, and authentication will be available at your disposal
Implementation:
Without further ado, I will walk you through the process of creating the coffee app.
Our work here is concentrated on the following parts:
- App.css — adjusts everything about UI style, color
- App.js — authentication, data model, business logic, and Sync
- Footer.js. — add optional information about the developer
- index.css. — add fonts and web page styling
As mentioned previously, React will be used as the library for our web app. Below are some options you can follow to create the project.
As mentioned previously, React will be used as the library for our web app. Below are some options you can follow to create the project.
Option 1 (the old-fashioned way): Create React App (CRA) has always been an “official” way to start a React project. However, it is no longer recommended in the React documents. The coffee app was originally developed using CRA. However, if you are coming from the older set-up or just wish to see how Device Sync is implemented within a React app, this will still be fine to follow.
Option 2: Vite addresses a major issue with CRA, the cumbersome dependency size, by introducing dependency pre-bundling. It provides lightning-fast, cold-starting performance.
If you already have your project built using CRA, there is also a fast way to make it Vite-compatible by using the code below.
npx nx@latest init
The line above will automatically detect your project’s dependency and structure and make it compatible with Vite. Your application will therefore also enjoy the high performance brought by Vite.
Our simple example app has most of its functionality within the
App.js
file. Therefore, let’s focus on this one and dive into the details.(1)
Dependency-wise, below are the necessary
imports
.Notice
realm
is being imported above as we need to do this to the source files where we need to interact with the database.(Consider using the
@realm/react
package to leverage hooks and providers for opening realms and managing the data. Refer to MongoDB’s other Web Sync Preview example app for how to integrate @realm/react.)(2)
To link your client app to an App Services app, you will need to supply the App ID within the code. The App ID is a unique identifier for each App Services app, and it will be needed as a reference while working with many MongoDB products.
Note: The client app refers to your actual web app whilst the App Services app refers to the app we create on the cloud, which resides on the Atlas App Services server.
You can easily copy your App ID from the App Services UI.
(3)
Now, let’s define the data model within our client app.
As you can see from the above snippet, the data model
CoffeeSchema
is written in JavaScript, and it looks no different than any other variables. However, it follows what we have in our back end, as shown below.If this rings a bell, you are totally correct! We discussed two ways to define data models in the previous section. In this case, we use the auto-generated data model from the schema section of the App Services UI. Alternatively, feel free to switch on “Development Mode” within the Device Sync panel and define the data model first in your client app. The server side will automatically adopt the model while this mode is on.
The caveat here is that Development Mode should almost never be used in a production environment for security reasons.
(4)
The core functionality of this web app is realized by Realm and its feature,
Device Sync
. Therefore, let’s first initiate Realm.In the code above, we also implemented the following components.
credentials
: App Services provides multiple authentication methods (anonymous, email/password, API keys, etc.). For simplicity reasons, we are usinganonymous
as this app’s authentication method. In a real production environment, the authentication method can be quickly switched to.apple
,.emailPassword
, etc. as listed in the documentation.- Sync
config
: Within thesync
block, we supply the information shown below.
user
: Passing in the user’s login credentials
flexible
: Defining what Sync mode the app will use
initialSubscriptions
: Defining the queries for the data that needs to be synced; the two parameters subs
and realm
refer to the sync’s subscriptions and local database instance.We now have built a crucial part that manages the data model used for Sync, authentication, sync mode, and subscription. This part customizes the initial data sync process and tailors it to fit the business logic.
(5)
Our coffee app calculates the cups of coffee we consume during the day. The simple app relies on inputs from the user. In this case, the data flowing in and out of the app is the number of different coffees the user consumes.
The UI here allows the user to increase/decrease the coffee drinks by hitting the
-
and +
rocks. We can build the component like this:The array of
drinksData
displays the name
and icon
of each coffee item.(6)
We now have the functional part of the UI built. However, the icons and buttons won’t do anything now. Let’s implement the logic behind all these UI components.
We will build a function component named
Apps()
to initialize the app, listen for changes in the coffee drinks amount, and clean up by closing the realm when no longer needed.
Here is an overview of the Apps()
function component.Let’s break it down.
On the app’s first launch,
useState(null)
will initialize the realm
variable with a null
value. The variable will later be used to save the instance of the opened Realm database.The latter line creates a piece of state (
drinksCount
) within a functional component. You can understand drinksCount
as a variable that holds the current number of coffee drinks while setDrinksCount
is a function which is called to updated the number of drinksCount
(a.k.a. state).In React, we describe the above structure as
Hook
. The state drinksCount
is “hooked by” the function setDrinksCount
and gets updated by it. When our user increases/decreases certain coffee drinks on the menu, these two lines are the heroes in the background.We then use “hook”
useEffect
to manage the connection to our local realm and listen to the coffee drinks’ changes in their amount.Notice (1), we use
mounted
to track whether the local realm is still active and therefore, the risk of updating a closed realm can be avoided.Notice (2), the “listener” we added responds to local coffee amount changes but that’s not all it does! Thanks to the flexible sync option we already set in the…
… the listener also reacts to changes on the server. Therefore, we can ensure Device Sync works in the background seamlessly.
Next up, let’s create a function to dynamically update the coffee drinks’ number and create new ones if those coffee drinks haven’t already been created in the back end.
First, check whether the realm is initialized.
Then,
write
to Realm if the specific coffee drink has already been created by searching the coffee object that matches the drinkName
.However, if the coffee drink hasn’t been consumed (created), let’s create it using
realm.create
.Let’s review the process and components implemented in our sample application in order to utilize Realm SDK and its device sync feature.
Initialize realm with the App ID
⇣
Open a synced realm
As described in the previous sections, this step contains all the setups, including authentication and sync configuration.
⇣
Listen to changes
⇣
Keep data updated
This is the journey of our data, from the user’s input to the local realm whilst the data updates are eventually synchronized with the back end (Atlas) with the help of the App Services server, where App Services provides services such as authentication, triggers, and functions.
1: What will our web app look like without Device Sync?
To answer this question, first, let’s take a look at the alternative implementation of our coffee app.
The app will still update the changes in coffee consumption to the back end. However, now this is done by using the function updateOne(), as shown by the code snippet below.
Here, we use
upsert
to update and insert the changed values of specific coffee drinks. As you can see, this code snippet works directly with documents stored in the back end. Instead of opening up a realm with the Device Sync feature, the coffee app without Device Sync still uses Web SDK.However, the above-described method is also known as “MongoDB Atlas Client.” The name itself is quite self-explanatory as it allows the client application to take advantage of Atlas features and access data from your app directly.
2: Which one should we choose?
Essentially, whether you should use the Device Sync feature from the Web SDK or follow the more traditional Atlas Client depends on your use cases, working environments, and existing codebase. We talked about two different ways to keep data updated between the client apps and the back end. Although both sample apps don’t look very different due to their simple functionality, this will be quite different in more complicated applications.
Look at the UI of both implementations of the web apps:
It is not hard to find the way the user interacts with the app is different. Device Sync no longer requires UI components such as “button” to interact with the user whilst the way the user interacts with the app using Atlas Client looks and feels more traditional. This requires the user’s interaction with different UI components such as buttons, checkboxes, etc.
Imagine an app with much more complex functionalities and UI. If used appropriately, Device Sync has the ability to bring a completely different user experience compared to the app updates data using Atlas Client. For example, a cooking recipe app will be able to show the user different finished products based on the amount/type of ingredients the user selects in real time. Without Device Sync, the app will still serve the same purpose. However, the user needs to confirm his/her choices manually. This won’t be a big issue, but the app loses a part of the intuitive user experience.
When making a choice between Atlas Client and Web SDK’s Device Sync, we need to consider an important aspect, which is the limitations of each method.
At the moment, Web SDK’s Device Sync is in its preview stage. Let’s first discuss the limitations of Device Sync on the web.
- No persistence: Web SDK doesn’t store sync data to the device’s local disk, which means all local data is stored in memory. Bear in mind that all will be lost when the user closes the browser tab or simply refreshes the web page.
- No multi-thread support: For now, all Realm operations need to be carried out on the app’s main thread. It isn’t possible to open or work with a realm in a web worker thread. This limitation may cause a slow UI response, which further leads to a negative user experience if not implemented with caution.
- No encryption at rest: You can understand this limitation as Realm JS Web SDK only encrypts data in transit between the browser and server over HTTPS. Anything that’s saved in the device’s memory will be stored in a non-encrypted format.
However, there’s no need to panic. As previously mentioned, Device Sync uses roles and rules to strictly control users’ access permissions to different data.
A limitation of Atlas Client is the way data is updated/downloaded between the client and server. Compared to Device Sync, Atlas Client does not have the ability to keep data synced automatically. This can also be seen as a feature, in some use cases, where data should only be synced manually.
In this article, we:
- Talked about the usage of the App Services Web SDK in a React web app.
- Compared Web SDK’s Device Sync feature against Atlas Client.
- Discussed which method we should choose.
The completed code examples are available in the appendix below. You can use them as live examples of MongoDB’s App Services Web SDK. As previously mentioned, the coffee apps are designed to be simple and straightforward when it comes to demoing the basic functionality of the Web SDK and its sync feature. It is also easy to add extra features and tailor the app’s source code according to your specific needs. For example:
- Instead of anonymous authentication, further configure
credentials
to use other more secure auth methods, such as email/password. - Modify the data model to fit your app’s theme. For now, our coffee app keeps track of coffee consumption. However, the app can be quickly rebuilt into a recipe app or something similar without complicated modifications and refactoring.
Alternatively, the example apps can also serve as starting points for your own web app project.
App Services’ Web SDK is MongoDB’s answer to developing modern web apps that take advantage of Realm (a local database) and Atlas (a cloud storage solution). Web SDK now supports Device Sync (in preview) whilst before the preview release, Atlas Client allowed web apps to modify and manipulate data on the server. Both of the solutions have the use cases where they are the best fit, and there is no “right answer” that you need to follow.
As a developer, a better choice can be made by first figuring out the purpose of the app and how you would like it to interact with users. If you already have been working on an existing project, it is beneficial to check whether you indeed need the background auto-syncing feature (Device Sync), compared to using queries to perform CRUD operations (Atlas Client). Take a look at our example app and notice the
App.js
file contains the basic components that are needed for Device Sync to work. Therefore, you will be able to decide whether it is a good idea to integrate Device Sync into your project.