Architecture
The not-so-obvious benefits of building offline-first applications
How an offline-first architecture can simplify data modeling, rule sharing, and the validation of new ideas.
By Vinícius Amélio
Some applications, by nature, have a core set of features that relies primarily on user interaction to create value. Their central idea is what we might call content-driven: task and project management apps, document editors, media galleries, note-taking apps, and similar products.
Even when these applications are collaborative by design, one of their strengths is that they can usually work offline. The degree of offline support will naturally vary from one product to another, but it is a common trait across this category.
Of course, not every digital product can work this way. Some products need to prioritize near real-time consistency over eventual consistency. That might even make for a good topic for a future post.
Consider an application that handles a balance, whether for a bank account, benefits, or cashback points. It cannot risk showing the user outdated information.
With that distinction in mind, I want to share some of my experience working on Physikos over the past year, particularly the productivity gains it brought to product development and testing. Many of those gains are not obvious at first glance.
Data schema
Applications that rely on the server as their source of truth usually require the backend to expose a set of integration contracts before a new feature can be developed. Ultimately, those contracts manipulate new data, or existing data in a new way, in a remote data source.
Building the client before that feature is ready means relying on mocks and working against a promise. This is not much of a problem when the same person owns the entire feature from end to end. When requirements change, however, you may eventually, and probably will, need to rework part of the implementation.
The situation is different when the application's primary data source runs locally on the user's device. The local database is already the source of truth and will eventually become consistent with the remote one.
The client can treat the state it actually has on hand as authoritative and apply optimistic updates. A rejection from the server still matters, of course, but it does not erase the current state.
These concerns come into play when you are designing something that already exists elsewhere. But what if it does not exist yet? When there is still nothing to synchronize, as is often the case at the beginning of a feature's development, the frontend is the only source of truth.
That lets us model data structures, business rules, and validations on the client first, then carry them over to the server.
If I have a workouts table and need to add an expiration field, I can simply add it to the app's schema and build the entire flow from there.
Once everything is settled, I can move those rules into another layer, assuming there is no shared way to run them already, such as WebAssembly, and manage the persistence on the backend as well.
Sharing rules
If your stack uses the same base language throughout, such as NestJS and React Native, you can easily create packages that share interfaces, validation rules, and business rules between both applications.
You could also create a DSL shared by the backend and frontend, although that can take a fair amount of work. Another option would be to use WebAssembly or an embeddable language and keep those rules there.
This is not strictly an offline-first benefit. Still, the need, or rather the convenience, of centralizing these rules creates a stronger point of consistency for bug fixes and for the additional handling the project needs as it evolves.
Faster proof-of-concept validation
The autonomy this approach gives a single developer also shortens the time needed to validate ideas because server-side development does not have to happen upfront.
I can quickly go from zero to a working feature and put it in the hands of the product and engineering teams. From there, I can iterate on it until we are ready to adopt it or discard it.
What matters is how much effort, and how many people, it takes to get something genuinely functional into someone's hands, without relying on mocks that fail to represent different states and behaviors.