IndexedDB
Service workers and background sync API, you can build an offline-friendly image upload system that cows upload and reintroduce them automatically-so your users can upload stress-free even when offline.
So you fill out an online form and it asks you to upload a file. You click Input, select a file from your desktop and are good to go. But something happens. The network drops, the file disappears and you are stuck by having to upload the file again. Poor network connection Can lead to you spending an unreasonable amount of time trying to upload files successfully.
What destroys the user experience stems from constantly having to control network stability and try again uploading several times. Although we may not be able to do much about networking, as developers, we can always do something to ease the pain that comes with this problem.
One of the ways we can solve this problem is by tuning image upload systems in a way that allows users to upload images offline – Removing the need for a reliable network connectionAnd then have the system again the upload process again when the network becomes stable without the user intervening.
This article will focus on explaining how to build An offline-friendly image upload system Using PWA (progressive web application) technologies such as IndexedDB
Service workers and background sync API. We will also briefly cover tips for improving the user experience to this system.
Planning of offline image upload system
Here is a flow chart for an offline-friendly image upload system.
As shown in the flow chart, the process takes place as follows:
- The user selects an image.
The process begins by letting the user choose their image. - The picture is stored locally in
IndexedDB
.
Next, checks the system for network connection. If network connection is available, the system uploads the image directly and avoids unnecessary local storage use. But if the network is not available, the image is stored inIndexedDB
. - The service worker detects when the network is restored.
With the picture tucked inIndexedDB
The system is waiting to detect when the network connection is restored to continue with the next step. - Background synchronization processes pending uploads.
The moment the connection is restored, the system will try to upload the image again. - The file is successfully uploaded.
In the moment the image is uploaded, the system removes the local copy that is stored inIndexedDB
.
Implementation of the system
The first step of the system implementation is to allow the user to choose their images. There are different ways you can achieve this:
I would recommend that you use both. Some users prefer to use the drag-and-slip interface while others think the only way to upload images is through element. Having both options will help improve the user experience. You may also consider allowing users to insert images directly into the browser using the clipboard API.
Registration of the service worker
In the heart of this solution is the service worker. Our service worker will be responsible for retrieving the picture from IndexedDB
Store, upload it when the Internet connection is restored, and clears IndexedDB
Store when the image is uploaded.
To use a service worker you must first register one:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(reg => console.log('Service Worker registered', reg))
.catch(err => console.error('Service Worker registration failed', err));
}
Control of network connection
Remember that the problem we are trying to solve unreliable network connection. If this problem does not exist, there is no sense in trying to solve something. Once the image is selected, we need to check if the user has a reliable internet connection before we detect a synchronization event and save the image in IndexedDB
.
function uploadImage() {
if (navigator.onLine) {
// Upload Image
} else {
// register Sync Event
// Store Images in IndexedDB
}
}
Note: I only use navigator.onLine
Property here to demonstrate how the system would work. The navigator.onLine
property is unreliableAnd I would suggest that you come up with a custom solution to check if the user is connected to the Internet or not. One way you can do this is by sending a ping request to a server point you have created.
Registration of the synchronization event
When the network test fails, the next step is to detect a synchronization event. The synchronization event must be registered at the point where the system does not upload the image due to a poor Internet connection.
async function registerSyncEvent() {
if ('SyncManager' in window) {
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('uploadImages');
console.log('Background Sync registered');
}
}
After registering the SYNC event, listen to it in the service worker.
self.addEventListener('sync', (event) => {
if (event.tag === 'uploadImages') {
event.waitUntil(sendImages());
}
});
The sendImages
The feature becomes an asynchronous process that will retrieve the image from IndexedDB
and upload it to the server. This is what it looks like:
async function sendImages() {
try {
// await image retrieval and upload
} catch (error) {
// throw error
}
}
Opening the database
The first thing we need to do to save our image locally is to open one IndexedDB
Store. As you can see from the below code we create A global variable to save the database body. The reason for doing this is that when we want to pick up our image from IndexedDB
We don’t have to write the code to open the database again.
let database; // Global variable to store the database instance
function openDatabase() {
return new Promise((resolve, reject) => {
if (database) return resolve(database); // Return existing database instance
const request = indexedDB.open("myDatabase", 1);
request.onerror = (event) => {
console.error("Database error:", event.target.error);
reject(event.target.error); // Reject the promise on error
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create the "images" object store if it doesn't exist.
if (!db.objectStoreNames.contains("images")) {
db.createObjectStore("images", { keyPath: "id" });
}
console.log("Database setup complete.");
};
request.onsuccess = (event) => {
database = event.target.result; // Store the database instance globally
resolve(database); // Resolve the promise with the database instance
};
});
}
Storing the image in indexeddb
With IndexedDB
Keep open, we can now save our pictures.
Now you may wonder why a lighter solution like
localStorage
were not used for this purpose.The reason for it is
IndexedDB
Works asynchronously and does not block the most important JavaScript wire, whereas there islocalStorage
Runs synchronously and can block JavaScript head thread if used.
How to save the picture in IndexedDB
:
async function storeImages(file) {
// Open the IndexedDB database.
const db = await openDatabase();
// Create a transaction with read and write access.
const transaction = db.transaction("images", "readwrite");
// Access the "images" object store.
const store = transaction.objectStore("images");
// Define the image record to be stored.
const imageRecord = {
id: IMAGE_ID, // a unique ID
image: file // Store the image file (Blob)
};
// Add the image record to the store.
const addRequest = store.add(imageRecord);
// Handle successful addition.
addRequest.onsuccess = () => console.log("Image added successfully!");
// Handle errors during insertion.
addRequest.onerror = (e) => console.error("Error storing image:", e.target.error);
}
With the images saved and background synchronization set, the system is ready to upload the image when the network connection is restored.
Retrieval and upload of the images
Once the network connection is restored, the synchronization event is shooting and the service worker retrieves the picture from IndexedDB
and upload it.
async function retrieveAndUploadImage(IMAGE_ID) {
try {
const db = await openDatabase(); // Ensure the database is open
const transaction = db.transaction("images", "readonly");
const store = transaction.objectStore("images");
const request = store.get(IMAGE_ID);
request.onsuccess = function (event) {
const image = event.target.result;
if (image) {
// upload Image to server here
} else {
console.log("No image found with ID:", IMAGE_ID);
}
};
request.onerror = () => {
console.error("Error retrieving image.");
};
} catch (error) {
console.error("Failed to open database:", error);
}
}
Deleting the indexeddb -database
Once the image has been uploaded, IndexedDB
Shop is no longer necessary. Therefore, it must be deleted with its contents to release storage.
function deleteDatabase() {
// Check if there's an open connection to the database.
if (database) {
database.close(); // Close the database connection
console.log("Database connection closed.");
}
// Request to delete the database named "myDatabase".
const deleteRequest = indexedDB.deleteDatabase("myDatabase");
// Handle successful deletion of the database.
deleteRequest.onsuccess = function () {
console.log("Database deleted successfully!");
};
// Handle errors that occur during the deletion process.
deleteRequest.onerror = function (event) {
console.error("Error deleting database:", event.target.error);
};
// Handle cases where the deletion is blocked (e.g., if there are still open connections).
deleteRequest.onblocked = function () {
console.warn("Database deletion blocked. Close open connections and try again.");
};
}
With that, the whole process is complete!
Considerations and limitations
While we have done a lot to help improve the experience by supporting offline uploads, the system is not without its limitations. I figured I would specifically call them out because it is worth knowing where this solution may be under your needs.
- No reliable Internet connection detection
JavaScript does not provide a foolproof way of detecting online status. For this reason, come up with a custom solution to detect online status. - Only chrome solution
Background synchronization API is currently limited to chrome -based browsers. As such, this solution is supported only by chrome browsers. This means you need a more robust solution if you have most of your users on non-chrome browsers. IndexedDB
Storage policies
Browsers impose storage restrictions and postponement policies forIndexedDB
. For example, in Safari that is stored data inIndexedDB
Have a lifetime of seven days if the user does not interact with the site. This is something you need to keep in mind if you come up with an alternative to the Background Sync API that supports Safari.
Improving the user experience
As the whole process happens in the background, we need a way to inform users when images are stored, waiting to be uploaded or have been successfully uploaded. Implementation of certain UI elements In fact, for this purpose will improve the experience of users. These UI elements may include toast notifications, upload status indicators such as spinners (to display active processes), status beams (to show state progress), network status indicators, or buttons to give tests and cancel options.
Wrapping
Poor Internet connection can interfere with the user experience of a web application. By utilizing PWA technologies such as IndexedDB
Service workers and background sync API can develop developers to improve the reliability of web applications to their users, especially those in areas of unreliable Internet connection.

(GG, YK)