- SajanTech's
- Posts
- Boost Your Web App's Performance with Background Sync for Seamless Offline Access
Boost Your Web App's Performance with Background Sync for Seamless Offline Access
Deliver High-Performance and Uninterrupted Access – Online or Offline
Introduction
Using IndexedDB a powerful, client-side storage API designed for handling large amounts of data can greatly enhance your web application’s performance. It’s particularly beneficial for tasks that require offline functionality, quick data access, or a reduced number of server requests.
In this article, I’ll demonstrate a straightforward example that combines IndexedDB with Service Workers to create an offline capable application with background sync. This approach leverages IndexedDB for local data storage and Service Workers for handling background tasks, providing a seamless and responsive experience, even when network connectivity is limited.
Prerequisite
This guide assumes readers have a basic understanding of JavaScript.
Our Application
We're creating a simple task management app that allows users to add, edit, and delete tasks while offline. Once the connection is restored, the app will automatically sync changes to the server in the background.
Setting up IndexedDB for storing data
First, create a utility for managing data storage in IndexedDB. This will include adding, fetching, and removing tasks. We use db.js
function openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("TaskDatabase", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore("tasks", { keyPath: "id", autoIncrement: true });
};
request.onsuccess = () => resolve(request.result);
request.onerror = (error) => reject(error);
});
}
async function addTaskToIndexedDB(task) {
try {
const db = await openDatabase();
const transaction = db.transaction("tasks", "readwrite");
transaction.objectStore("tasks").add(task);
return transaction.complete;
}catch(e) {
// Handle errors here..
console.error(e);
throw new Error("Failed to write the task! because:" + e.message);
}
}
async function getTasksFromIndexedDB() {
try {
const db = await openDatabase();
const transaction = db.transaction("tasks", "readonly");
const store = transaction.objectStore("tasks");
return store.getAll();
}catch(e) {
// Handle errors here..
console.error(e);
throw new Error("Failed to read! because:" + e.message);
}
}
async function deleteTaskFromIndexedDB(id) {
try {
const db = await openDatabase();
const transaction = db.transaction("tasks", "readwrite");
transaction.objectStore("tasks").delete(id);
return transaction.complete;
}catch(e) {
// Handle errors here..
console.error(e);
throw new Error("Failed to delete the task! because:" + e.message);
}
}
The above code provides a utility for managing tasks offline using IndexedDB. It includes functions to:
Open or create a database called
TaskDatabase
with a tasksstore
.Add tasks to the database
addTaskToIndexedDB
.Retrieve all stored tasks
getTasksFromIndexedDB
.Delete specific tasks by ID
deleteTaskFromIndexedDB
.
Together, these functions enable offline storage and retrieval of tasks, with background syncing possible when the connection is restored.
Setting up Service Worker for Background Sync
Next, register a service worker to monitor for sync events. This service worker will automatically sync stored data with the server as soon as an internet connection is available.
In task-service-workert.js
self.addEventListener("sync", (event) => {
if (event.tag === "sync-tasks") {
event.waitUntil(syncTasks());
}
});
async function syncTasks() {
// Retrieve tasks from IndexedDB
const tasks = await getTasksFromIndexedDB();
// Send each task to the server (assuming a POST endpoint exists)
const syncPromises = tasks.map(async (task) => {
try {
const response = await fetch("/api/tasks", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(task),
});
if (response.ok) {
// If successfully synced, delete the task from IndexedDB
await deleteTaskFromIndexedDB(task.id);
}
} catch (error) {
console.error("Sync failed for task:", task, error);
}
});
return Promise.all(syncPromises);
}
Service Worker Sync Event Listener
The code listens for a sync event in the service worker. When triggered (e.g., after a lost connection is restored), it checks if the event tag is sync-tasks. If it matches, the syncTasks
function runs.
syncTasks
Function
Retrieves tasks stored in IndexedDB.
Sends each task to the server by making a POST request to an API endpoint ("/api/tasks").
If a task syncs successfully, it’s deleted from IndexedDB.
If a sync fails, it logs an error but leaves the task in IndexedDB for future sync attempts.
Register the Service Worker and Enable Background Sync
Register the service worker and request a background sync for tasks whenever new data is added. This ensures that tasks will automatically sync with the server when a connection is available.
in task-main.js
if ("serviceWorker" in navigator && "SyncManager" in window) {
navigator.serviceWorker.register("/task-service-workert.js").then((registration) => {
console.log("Service Worker registered:", registration);
});
}
async function addTask(task) {
// Save task in IndexedDB for offline use
await addTaskToIndexedDB(task);
// Request a background sync
if ("serviceWorker" in navigator && "SyncManager" in window) {
const registration = await navigator.serviceWorker.ready;
await registration.sync.register("sync-tasks");
} else {
// If background sync isn't available, try to sync immediately.
await syncTasks();
}
}
The above code registers a service worker to manage background syncing and adds tasks to IndexedDB. Each time a new task is added, it requests a background sync to send tasks to the server when online. If background sync isn’t supported, it attempts an immediate sync.
in index.html
<div>
<form id="taskForm" onsubmit="handleFormSubmit(event)">
<div>
<input type="text" placeholder="Title" name="title" required />
</div>
<div>
<textarea rows="10" cols="20" name="description" placeholder="Description" required></textarea>
</div>
<div>
<input type="date" id="start" name="dueDate" required />
</div>
<div>
<button type="submit">Add Task</button>
</div>
</form>
</div>
task-main.js
async function handleFormSubmit(event) {
event.preventDefault(); // Prevent form from refreshing the page
// Get form data
const form = event.target;
const task = {
title: form.title.value,
description: form.description.value,
dueDate: form.dueDate.value,
};
// Save task to IndexedDB and request background sync
await addTask(task);
// Clear the form fields
form.reset();
}
Breakdown of the above code
Form Submission: When a user submits the form, handleFormSubmit is triggered, preventing the default page reload.
Task Creation: The form inputs are used to create a
task
object containing the task details.Saving and Syncing: The
addTask(task)
function is called to:Save the task in IndexedDB for offline use.
Automatically request a background sync, so the task will sync with the server as soon as an internet connection is available.
Form Reset: The form fields are cleared after each submission, preparing it for new task entries.
This approach ensures that each new task is saved locally and queued for background sync whenever possible, creating a seamless experience for offline task management.
Optional code for the server API
On the server side, set up an API endpoint to accept tasks sent from the client.
in server.js
const express = require("express");
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 8080;
app.post("/api/tasks", (req, res) => {
const task = req.body;
// Save task to database (simulate here with a success response)
console.log("Task synced:", task);
res.status(201).json({ message: "Task synced successfully!" });
});
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Conclusion
This setup enables users to add tasks even when offline, storing them in IndexedDB for later syncing. Once a connection is available, the service worker's background sync feature automatically pushes the tasks to the server, ensuring data consistency without any additional action needed from the user.
Imagine building powerful, offline-ready web apps that work seamlessly for your users whether they’re online or off. With our newsletter, you’ll stay on top of the latest tips, tricks, and techniques for building robust applications like the one we just created, where IndexedDB, Service Workers, and background sync come together to deliver smooth, reliable performance.
By subscribing, you’ll unlock expert advice, practical code snippets, and insider knowledge that will transform your coding skills. Don’t just keep up lead the way in web development! Join my community as a forward-thinking developers today, and never miss out on the insights that keep your projects one step ahead.
Subscribe now, and get ready to level up!
Reply