Skip to main content
Mini POS is built as a Progressive Web App (PWA) with complete offline functionality, allowing it to work reliably without an internet connection.

Overview

The offline-first architecture ensures that:
  • All features work without internet connectivity
  • Data is stored locally on the device
  • The app can be installed like a native application
  • Assets are cached for instant loading

Service Worker

Caches all app assets and enables offline operation

IndexedDB

Stores all products and sales data locally

PWA Install

Add to home screen on mobile or desktop

Cache Strategy

Cache-first approach with network fallback

Service Worker Architecture

The service worker implements a complete caching strategy:
sw.js
// Bump cache version to force service worker update when changed
const CACHE = 'mini-pos-v2';
const ASSETS = [
  './',
  './index.html',
  './products.html',
  './pos.html',
  './reports.html',
  './manifest.json',
  './css/style.css',
  './js/db.js',
  './js/shared.js',
  './js/dashboard.js',
  './js/products.js',
  './js/pos.js',
  './js/reports.js',
  './icons/icon-192.png',
  './icons/icon-512.png',
];
The cache version (CACHE) is incremented whenever assets change, forcing the service worker to update and clear old caches.

Installation Phase

During installation, the service worker pre-caches all essential assets:
sw.js
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE).then((cache) =>
      Promise.allSettled(
        ASSETS.map((asset) => cache.add(asset).catch(() => {
          // ignore individual asset cache errors
        }))
      )
    )
  );
  self.skipWaiting();
});
skipWaiting() ensures the new service worker activates immediately, providing users with the latest version without manual refresh.

Activation and Cache Cleanup

Old caches are automatically removed when a new version activates:
sw.js
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(
        keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))
      )
    )
  );
  self.clients.claim();
});

Fetch Strategy

The service worker uses a cache-first strategy with network fallback:
sw.js
self.addEventListener('fetch', (event) => {
  const req = event.request;
  if (req.method !== 'GET') return;
  
  event.respondWith(
    caches.match(req).then((hit) => {
      if (hit) return hit;
      return fetch(req)
        .then((res) => {
          const resClone = res.clone();
          caches.open(CACHE).then((cache) => cache.put(req, resClone)).catch(() => {});
          return res;
        })
        .catch(() => caches.match('./index.html'));
    })
  );
});

Strategy Breakdown

  1. Check Cache First: Look for the requested resource in the cache
  2. Return Cached: If found, serve from cache immediately (no network delay)
  3. Fetch from Network: If not cached, try to fetch from the network
  4. Update Cache: Cache the network response for future use
  5. Fallback: If offline and resource not cached, serve the index page
Only GET requests are intercepted. POST, PUT, DELETE requests pass through directly to the network (though IndexedDB handles all data operations locally).

Service Worker Registration

The app registers the service worker on page load:
js/shared.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('./sw.js', { scope: './' })
      .catch(console.error);
  });
}
Registration happens after the page loads to avoid delaying initial render.

PWA Installation

The app can be installed on devices that support PWA installation:
js/shared.js
let deferredPrompt;

window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  deferredPrompt = e;
  const installBtn = document.getElementById('installBtn');
  if (installBtn) {
    installBtn.classList.remove('hidden');
    installBtn.addEventListener('click', async () => {
      if (!deferredPrompt) return;
      deferredPrompt.prompt();
      await deferredPrompt.userChoice;
      deferredPrompt = null;
      installBtn.classList.add('hidden');
    });
  }
});

Installation Flow

  1. Browser detects PWA is installable
  2. beforeinstallprompt event fires
  3. Install button becomes visible in the header
  4. User clicks “Install App”
  5. Browser shows native install prompt
  6. App is added to home screen/application menu
  7. Install button hides automatically
For a PWA to be installable, it must:
  • Be served over HTTPS (or localhost for development)
  • Have a valid manifest.json with icons and name
  • Have a registered service worker
  • Meet the browser’s engagement heuristics

Local Data Storage

All application data is stored in IndexedDB:
js/db.js
const DB_NAME = 'pos-db';
const DB_VER = 1;

function openDB() {
  return new Promise((resolve, reject) => {
    const req = indexedDB.open(DB_NAME, DB_VER);
    req.onupgradeneeded = () => {
      const db = req.result;
      if (!db.objectStoreNames.contains('products')) {
        db.createObjectStore('products', { keyPath: 'id', autoIncrement: true });
      }
      if (!db.objectStoreNames.contains('sales')) {
        const store = db.createObjectStore('sales', { keyPath: 'id', autoIncrement: true });
        store.createIndex('date', 'date');
      }
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}

Storage Benefits

  • No Server Required: All data stays on the device
  • Fast Access: IndexedDB queries are extremely fast
  • Large Capacity: Can store thousands of products and transactions
  • Structured Data: Supports complex objects with indexes
  • Transactional: ACID-compliant for data integrity
IndexedDB is persistent storage that survives browser restarts and even device reboots.

Offline Capabilities

What Works Offline

All Core Features:
  • View dashboard and statistics
  • Add, view, and delete products
  • Process sales transactions
  • Generate and print receipts
  • View sales reports
  • Export data to CSV
  • Print PDF reports
Data Persistence:
  • All products and sales are saved locally
  • No data loss when offline
  • Immediate read/write operations
UI and Assets:
  • Complete interface loads from cache
  • All pages navigate instantly
  • Icons and styles cached

What Requires Network

External Resources (if any):
  • External CDN scripts (app uses local CSS)
  • Remote API calls (app doesn’t make any)
  • Cloud sync (not implemented)
Mini POS is designed to work 100% offline with zero network dependencies after initial load.

Cache Management

Updating the App

When you make changes and want to deploy a new version:
  1. Increment the cache version:
    const CACHE = 'mini-pos-v3'; // was v2
    
  2. The service worker will:
    • Install with the new cache version
    • Pre-cache all assets again
    • Activate and delete old caches
    • Serve the new version

Manual Cache Clear

Users can clear the cache via browser settings:
  • Chrome: Settings → Privacy → Clear browsing data
  • Firefox: Settings → Privacy & Security → Cookies and Site Data
  • Safari: Settings → Safari → Clear History and Website Data
Clearing cache doesn’t delete IndexedDB data. Products and sales remain intact.

PWA Manifest

The manifest.json defines how the app appears when installed:
manifest.json
{
  "name": "Mini POS System",
  "short_name": "Mini POS",
  "description": "Lightweight Point of Sale System",
  "start_url": "./",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#4f46e5",
  "icons": [
    {
      "src": "./icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "./icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

Best Practices

For Developers

  1. Version Bumping: Always increment cache version after changes
  2. Asset List: Keep the ASSETS array updated with all files
  3. Testing Offline: Use Chrome DevTools → Application → Service Workers → Offline
  4. Cache Size: Monitor cache size to avoid exceeding browser limits
  5. Error Handling: Use Promise.allSettled to handle partial cache failures

For Users

  1. Install the App: Get native app experience with offline support
  2. Initial Load: Ensure first visit completes to cache all assets
  3. Regular Use: App works seamlessly whether online or offline
  4. Data Backup: Export reports regularly as browser data can be cleared

Troubleshooting

Problem: Changes don’t appear after deploymentSolutions:
  • Verify cache version was incremented
  • Hard refresh (Ctrl+Shift+R or Cmd+Shift+R)
  • Clear browser cache manually
  • Unregister service worker in DevTools and reload
Problem: PWA install prompt doesn’t appearSolutions:
  • Ensure HTTPS is enabled (or using localhost)
  • Check manifest.json is valid and linked
  • Verify service worker registered successfully
  • Check browser console for errors
  • Some browsers require minimum engagement before showing prompt
Problem: Products or sales disappearSolutions:
  • Check browser storage settings (allow sites to save data)
  • Verify IndexedDB isn’t being cleared by privacy extensions
  • Ensure browser isn’t in private/incognito mode
  • Check available storage space

Browser Support

  • Chrome/Edge: Full PWA support including install
  • Firefox: Service worker and caching work, limited install UI
  • Safari: iOS 11.3+ supports PWA, add to home screen
  • Opera: Full PWA support
All modern browsers support IndexedDB and service workers, ensuring the app works offline everywhere.

Build docs developers (and LLMs) love