Sometimes Connectivity Becomes a Bottleneck.
In enterprise environments, we often take stable internet connectivity as something given. However, real world conditions frequently challenge this assumption, potentially disrupting critical business operations. This article details how we transformed a traditional online only ERP system into a more reliable system with resilient offline capable solution. By leveraging browser-based storage solutions like IndexedDB, employing synchronization mechanisms, and using Progressive Web Apps (PWA).
Initially, the system followed a traditional client server architecture where all business logic resided on the backend. While this architecture works well in environments with reliable connectivity, it presented several challenges:
- Transaction failures during network interruptions
- Lost sales opportunities during outages
- Poor user experience with constant loading states
- Risk of data loss during critical operations
- And most importantly, loosing customers due to lack of swift service.
So defining this, we had to improvise and see how we can make things better and also do without connecrivity since it isnt available initially, we implemented an offline system with some internet required using Progressive Web Apps (PWA), effectively moving critical business logic to the frontend while maintaining data integrity and synchronization with the core ERP system.
Some Core Components:
IndexedDB: For offline data storage and caching, we used IndexedDB via the Dexie.js library to provide a robust, client-side database that supports structured data storage. below is a simple example of how to set up a database with Dexie
// Initialize IndexedDB using Dexie.js
import Dexie from 'dexie';
interface LocalUsers{
id:string;
name:string;
role:string;
dob:string;
phone_no:string
}
interface LocalTrx {
id: string;
syncId:string;
created_at:string;
amount:string;
isSynced:boolean;
modified:string;
}
export class ArticleDatabase extends Dexie {
transaction!: Table<LocalTrx>;
users!: Table<LocalUsers>;
constructor(){
super("articleDB")
}
this.version(1).stores({
// define the fields you'll like to query by or find items by within the indexDB
transactions: 'id, date, amount, isSynced',
users: 'id, name, role'
});
}
//create the db
export const db=new ArticleDatabase()
// Open the database
db.open().then(() => {
console.log("Database opened successfully!");
})
.catch((error) => {
console.error("Failed to open database:", error);
});
// Adding a new transaction to IndexedDB
import db from ../db
async function addTransaction(transaction) {
try {
const trx = await db.transactions.add(transaction)
console.log("Trx added", trx)
} catch (err) {
console.log("Failed creating trx", err)
}
}
Service Workers: These act as a proxy between the app and the network, enabling offline functionality by caching resources and intercepting requests to ensure that critical data remains accessible during disconnection.
//service workesr can be setup easily, recently by default nextJS apps do come with service workes with vite, you can use the vite-pwa plugin
Background Sync: This allows us to sync data once the network is available again, ensuring that transactions are not lost and updates are made automatically once connectivity is restored.
System Architecture Flow
The architecture was divided into three main phases: initialization, transaction processing, and synchronization. The flowchart below shows how data flows between these stages.
*Initialization Phase *
When the system starts, it checks the network connection:
If the device is online, it fetches the latest master data from the server and updates the local IndexedDB.
If the device is offline, it loads data from IndexedDB, ensuring that users can continue working without disruption.
Transaction Processing
When users perform a new transaction:
Local data is validated and stored in IndexedDB.
An optimistic UI update is used to immediately show the result to the user, providing a smooth and responsive experience.
*Synchronization Phase *
When connectivity is restored:
Data is synchronized in batches with the server, either by manually clicking the sync button or after some certain timeframe.
If the sync fails (e.g., due to a slow connection), the transaction is added to a list of failed transactions and retried later.
Since we manage everything on the frontend how reliant is our service against securing customers Information.
Authentication & Authorization
In any enterprise system, securing sensitive user information is critical. Our solution ensures that:
JWT-based Authentication is used for secure user sessions.
Role-based access control ensures that only authorized users can perform specific actions.
Secure token storage is handled using browser-based mechanisms such as localStorage for added security.
To mitigate the risks of using locally stored tokens, we:
Trigger safe removal of user tokens upon logout.
Ensure that sensitive data is deleted from IndexedDB when the session ends or when the user logout from the system. Note: if transactions are left unsynced we display that to the logged in user and enforce them to sync before they logout.
Data Integrity & Conflict Resolution
Syncing data between the client and server introduces potential issues with data integrity, especially if multiple devices or users are making changes to the same data offline. To address this:
We validate all transaction details (e.g., quantities, amounts) before syncing to ensure that there are no discrepancies.
We assign unique IDs to each transaction to prevent duplication during synchronization.
Conflict resolution strategies are employed to handle situations where data changes are made on multiple devices while offline. For example, we use a timestamp approach.
//we try to make sure the offline is considered first, because it's the important bit of the system.
async function resolveConflict(localTransaction, serverTransaction) {
// Compare timestamps determine which transaction should prevail
if (new Date(localTransaction.timestamp) > new Date(serverTransaction.timestamp)) {
// Local transaction wins
await syncTransactionWithServer(localTransaction);
} else {
// Server transaction wins
await updateLocalTransaction(serverTransaction);
}
}
async function updateLocalTransaction(transaction) {
// Update the local transaction in IndexedDB to reflect the server's state
await db.transactions.put(transaction);
}
Network Security
Given that data is transmitted over the network once connectivity is restored, we ensured:
Rate limiting to prevent abuse and ensure that too many requests do not overwhelm the server with a 429 response hence why we originally worked with batch updates.
Encryption of data during transit using SSL/TLS.
Token expiration and secure token management, ensuring that stale or expired tokens are automatically removed from the client-side storage.
Alternatives to PWA & IndexedDB
While IndexedDB is a solid choice for client-side data storage in PWAs, there are other options available depending on the application's complexity and requirements:
SQLite via WebAssembly (WASM): Some developers opt to use SQLite via WASM for more advanced data management, particularly when dealing with larger datasets or complex queries. However, integrating SQLite via WASM introduces additional complexity, such as performance concerns and browser compatibility (e.g How sqlite made Notion faster).
Web Storage API (localStorage/sessionStorage): For simpler applications that don’t require complex querying or large datasets, the Web Storage API may be a viable alternative. It is easier to implement but has limitations in terms of storage capacity and querying capabilities.
Looking Ahead: Future Trends in PWA
As web technologies continue to evolve, so do the possibilities for applications like this. Emerging trends include:
- WebAssembly and SQLite
- Edge Computing
- Advanced Sync Protocols: Emerging protocols like CRDTs (Conflict-Free Replicated Data Types) and DeltaSync
I myself can’t wait to explore how these technologies will transform the landscape of offline and distributed applications. With the rapid advancements in powerful machines and laptops, we have the opportunity to harness this increased computing power to deliver even more sophisticated and efficient software for users. At the same time, we must not forget the importance of catering to mobile devices and less capable devices, ensuring that our solutions are accessible and optimized across all platforms. The potential is enormous, and I’m excited to continue pushing the boundaries of what's possible with PWAs.
Note: What's Next
We’ll be taking things up. Using Djuix.io as our backend and React / Angular for our frontend, we would implement a proper basic flow. Stay tuned for more updates as we continue to enhance our approach to building amazing apps.
Anyway, I hope you enjoyed this and learned something new. I certainly did. I'd also love to hear your thoughts and experiences.
Until then.
Top comments (0)