
PWA: Web as App
PWA characteristics and implementation

PWA characteristics and implementation
Why REST still dominates, what GraphQL actually solves, and when gRPC really shines. A practical breakdown of all three API protocols with real code and decision criteria.

Kubernetes looks overwhelming at first glance. This post breaks down what Pods, ReplicaSets, Deployments, Services, Ingress, ConfigMaps, and Secrets actually do and how they connect — with practical YAML examples and local dev setup.

Converting words and sentences into numeric vectors lets you do math on meaning. From cosine similarity and ANN algorithms to the OpenAI embeddings API, here's everything you need to know.

Once you ship a public API, you can't change it freely. Compare four versioning strategies for evolving APIs without breaking clients, plus analysis of real-world choices by GitHub, Stripe, and Twilio.

While building my service, I had a problem. Users kept requesting, "I want to add this to my home screen." But making a native app... just thinking about it gave me a headache.
I thought, "I already built it as a web app, do I really need to make a native app too?" Then I discovered PWA (Progressive Web App). They said it's technology that makes web work like an app.
I was skeptical at first. "How can web become like an app?" But when I actually built one... I was amazed.
The first thing I tackled with PWA was offline support. Regular websites just show the "No internet connection" dinosaur game when offline. But PWAs are supposed to work offline.
I wondered how this was possible, and the key was Service Worker. Simply put, it's a JavaScript file that acts as a bridge between the browser and server. It intercepts network requests, checks cached data first, and only requests from the server if not found.
When I first registered a Service Worker, I did it like this:
// main.js - Register Service Worker
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker registered:', registration);
})
.catch(error => {
console.log('Service Worker registration failed:', error);
});
});
}
And the actual Service Worker file looked like this:
// sw.js - Service Worker implementation
const CACHE_NAME = 'my-pwa-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/logo.png'
];
// Install phase: Save necessary files to cache
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('Cache opened');
return cache.addAll(urlsToCache);
})
);
});
// Intercept network requests
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
// Return cached data if available
if (response) {
return response;
}
// Otherwise fetch from network
return fetch(event.request);
})
);
});
After building this, my website worked even when I disconnected the internet! I was genuinely amazed when I first saw it. "Wow, this actually works?"
After implementing offline support with Service Worker, the next step was add to home screen. When users tap "Add to Home Screen" in the browser menu, an icon appears just like a real app.
For this, I needed a Web App Manifest file. One JSON file defines the app's name, icon, colors, etc.:
{
"name": "My Service",
"short_name": "Service",
"description": "My service built as PWA",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2196F3",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Then link this manifest file in HTML:
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#2196F3">
After this, when I opened my site on mobile, an "Add to Home Screen" popup appeared. When added, an icon appeared just like a real app, and clicking it opened in fullscreen without the browser address bar.
"display": "standalone" was the key. This hides the browser UI and makes it look like an app.
The most surprising thing about PWA was push notifications. Sending push notifications from a website! This was also thanks to Service Worker.
First, request notification permission from the user:
// Request notification permission
async function requestNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
console.log('Notification permission granted');
// Register push subscription
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: 'YOUR_PUBLIC_KEY'
});
// Send subscription to server
await sendSubscriptionToServer(subscription);
}
}
Then handle push messages in Service Worker and display notifications:
// sw.js - Handle push notifications
self.addEventListener('push', (event) => {
const data = event.data.json();
const options = {
body: data.body,
icon: '/icons/icon-192x192.png',
badge: '/icons/badge-72x72.png',
vibrate: [200, 100, 200],
data: {
url: data.url
}
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
// Handle notification click
self.addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
});
With this, I could send notifications even when users weren't viewing my site. Just like a native app!
An important concept I learned while using Service Worker was caching strategies. Caching everything is good for offline but prevents updates, while caching nothing makes it unusable offline.
The strategy I used:
1. Cache First: Static files like images, CSS, JS
self.addEventListener('fetch', (event) => {
if (event.request.url.match(/\.(jpg|png|css|js)$/)) {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
}
});
2. Network First: Dynamic content like API data
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/')) {
event.respondWith(
fetch(event.request)
.then(response => {
// Save network response to cache
const responseClone = response.clone();
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
// Use cache on network failure
return caches.match(event.request);
})
);
}
});
This way, static files load quickly, API data stays fresh, and users can still see the last data offline.
After building a PWA and applying it to my service, the pros and cons were clear.
PWA is technology that makes web work like an app using Service Worker and Web App Manifest. It enables app features like offline support, add to home screen, and push notifications on the web, and deployment without app stores makes development and updates easy. While not as powerful as native apps, it's sufficient for most web services and, most importantly, much easier to build.