Sometimes connectivity can become a bottleneck.
In a corporate environment, we often take a stable network connection as a given. However, real-world situations often challenge this assumption and can disrupt critical business operations. This article details how we transformed a traditional online ERP system into a more reliable system with a resilient offline solution. By leveraging browser-based storage solutions such as IndexedDB, using 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 an environment with reliable connectivity, it also presents some challenges:
- Transactions failed during network outage
- Sales opportunities lost during outages
- Continuous loading state leads to poor user experience
- Risk of data loss during critical operations
- On top of that, losing customers due to lack of fast service.
So, defining this, we had to improvise and see how we could make things better and to do it without connectivity, since it was not available initially, we implemented a need using Progressive Web Apps (PWA) Some Internet-based offline systems effectively move key business logic to the front end while maintaining data integrity and synchronization with the core ERP system.
Some core components:
Index database: For offline data storage and caching, we use IndexedDB through the Dexie.js library to provide a powerful client-side database that supports structured data storage. The following is a simple example of how to set up a database using 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;
users!: Table;
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 staff: They act as a proxy between the application and the network, enabling offline functionality by caching resources and intercepting requests to ensure critical data remains accessible during disconnections.
//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 synchronize data when the network is available again, ensuring transactions are not lost and automatically updated when connectivity is restored.
System architecture process
This architecture is 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 will get the latest master data from the server and update the local IndexedDB.
If the device goes offline, it loads data from IndexedDB, ensuring users can continue working without interruption.
transaction processing
When a user executes a new transaction:
Local data is verified and stored in IndexedDB.
Optimistic UI updates are used to display results to users immediately, providing a smooth and responsive experience.
*Synchronization phase *
When the connection is restored:
Data is synchronized with the server manually by clicking the sync button or in batches after a certain time range.
If synchronization fails (for example, due to a slow connection), the transaction will be added to the list of failed transactions and retried later.
Since we manage everything on the front end, how reliant our services are on protecting our customers’ information.
Authentication and Authorization
In any enterprise system, protecting sensitive user information is critical. Our solutions ensure:
JWT based authentication Used for secure user sessions.
role-based access control Ensure that only authorized users can perform specific operations.
Security token storage Processed using browser-based mechanisms such as localStorage for increased security.
To reduce the risk of using locally stored tokens, we:
Trigger safe deletion of user token on logout.
Ensure that sensitive data is deleted from IndexedDB when the session ends or when the user logs out of the system. Note: If transactions are not synced, we will display this to logged in users and force them to sync before logging out.
Data integrity and conflict resolution
Synchronizing data between client and server introduces potential issues with data integrity, especially if multiple devices or users change the same data offline. To solve this problem:
We verify all transaction details (e.g. quantity, amount) before syncing to ensure there are no discrepancies.
We assign a unique ID to each transaction to prevent duplication during synchronization.
Use conflict resolution strategies to handle data changes on multiple devices while offline. For example, we use timestamp method.
//We try to make sure offline is considered first as it is an important part 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);
}
Internet security
Since data will be transferred over the network once the connection is restored, we ensure that:
Rate limiting is to prevent abuse and ensure that too many requests don’t overwhelm the server with 429 responses, which is why we use bulk updates in the first place.
Use SSL/TLS to encrypt data during transmission.
Token expiration and secure token management, ensuring that stale or expired tokens are automatically removed from client storage.
Alternatives to PWA and IndexedDB
While IndexedDB is a solid choice for client-side data storage in PWAs, there are other options available depending on the complexity and requirements of the application:
SQLite via WebAssembly (WASM): Some developers choose to use SQLite through WASM for more advanced data management, especially when dealing with larger data sets or complex queries. However, integrating SQLite through WASM brings additional complexities, such as performance issues and browser compatibility (eg how sqlite makes Notion faster).
Web storage API (localStorage/sessionStorage): For simple applications that don’t require complex queries or large data sets, the Web Storage API may be a viable alternative. It is easier to implement, but has limitations in storage capacity and query capabilities.
Looking ahead: Future trends for PWAs
As web technology continues to evolve, so do the possibilities for such applications. Emerging trends include:
- WebAssembly and SQLite
- Edge operations
- Advanced synchronization protocols: emerging protocols such as CRDT (Conflict-Free Replicated Data Type) and DeltaSync
I myself can’t wait to explore how these technologies will change the offline and decentralized application landscape. With the rapid development of powerful machines and laptops, we have the opportunity to leverage this enhanced computing power to provide users with more complex and efficient software. At the same time, we must not forget the importance of catering to mobile and less powerful devices, ensuring our solutions are accessible and optimized on all platforms. The potential is huge, and I’m excited to continue pushing the boundaries of what’s possible with PWAs.
Note: What’s next?
We’ll take care of things. use Djuix.io We will implement a proper basic process with React/Angular as our backend and our frontend. 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. Of course I did. I’d also love to hear your thoughts and experiences.
Until then.