· deepdives · 6 min read
Exploring the Web Bluetooth API: Building Real-Time IoT Applications
Learn how to use the Web Bluetooth API to connect web apps to Bluetooth Low Energy devices, read sensor data in real time, send commands, and build robust IoT dashboards with step-by-step code examples and best practices.

Why Web Bluetooth for IoT?
The Web Bluetooth API brings Bluetooth Low Energy (BLE) device access directly into web pages. That means you can build real-time IoT dashboards, configuration tools, or control panels that run in a browser-without native apps. Use cases include:
- Live sensor dashboards (temperature, humidity, motion)
- Device configuration UIs (firmware settings, calibration)
- Remote control (robotics, lighting)
- Diagnostics and firmware over-the-air coordination
Before we begin, note the key caveats: browsers require HTTPS, user gestures to initiate device selection, and support varies (Chrome/Edge on desktop and Android have the best support; Safari/iOS has limited or no support as of this writing). See browser compatibility in the references below.
References: MDN Web Bluetooth API and Google’s Web Bluetooth guide
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API
- https://developer.chrome.com/articles/bluetooth-web/
Quick primer: BLE, GATT, services and characteristics
Bluetooth Low Energy devices expose a GATT (Generic Attribute Profile) server made of services and characteristics:
- Service: a grouping of related characteristics (e.g., Health Thermometer Service)
- Characteristic: a single data value (read/notify/write) with a UUID
- Descriptor: metadata about a characteristic
Real-time data typically uses “notifications”: the device pushes updates, and the browser receives them via a JavaScript callback.
Step-by-step: Connect and read notifications (practical example)
We’ll build a small example that connects to a BLE device exposing a temperature characteristic, subscribes to notifications, and updates the UI.
HTML (minimal):
<!-- index.html -->
<button id="connect">Connect to Device</button>
<div id="status">Disconnected</div>
<div id="temperature">-</div>
JavaScript (connect, subscribe, decode values):
// app.js
const connectBtn = document.getElementById('connect');
const statusEl = document.getElementById('status');
const tempEl = document.getElementById('temperature');
// Replace with the service and characteristic UUIDs your device uses
const SERVICE_UUID = 'health_thermometer'.toLowerCase(); // example alias
const CHARACTERISTIC_UUID = 'temperature_measurement';
let device, server, characteristic;
async function onConnectClick() {
try {
// Request device with optional filters to reduce the list
device = await navigator.bluetooth.requestDevice({
filters: [{ services: [SERVICE_UUID] }],
// Or use acceptAllDevices: true with caution
optionalServices: [SERVICE_UUID],
});
device.addEventListener('gattserverdisconnected', handleDisconnect);
statusEl.textContent = `Connecting to ${device.name || device.id}...`;
server = await device.gatt.connect();
const service = await server.getPrimaryService(SERVICE_UUID);
characteristic = await service.getCharacteristic(CHARACTERISTIC_UUID);
// Start notifications (real-time updates)
await characteristic.startNotifications();
characteristic.addEventListener(
'characteristicvaluechanged',
handleNotifications
);
statusEl.textContent = 'Connected';
} catch (err) {
console.error('Connection failed', err);
statusEl.textContent = 'Connection failed: ' + err.message;
}
}
function handleNotifications(event) {
const value = event.target.value; // DataView
// Decode according to your characteristic's spec. For example, a 32-bit float:
const tempC = value.getFloat32(0, /*littleEndian=*/ true);
tempEl.textContent = tempC.toFixed(2) + ' °C';
}
function handleDisconnect() {
statusEl.textContent = 'Disconnected';
}
connectBtn.addEventListener('click', onConnectClick);
Notes:
- Use the correct UUIDs for your device. Many devices use standard services (e.g., Health Thermometer) or vendor-specific 128-bit UUIDs.
characteristicvaluechanged
delivers a DataView; decode according to the device’s specification.
Example: Nordic UART Service (bi-directional serial)
Many IoT devices implement a serial-like service-Nordic’s NUS-for bidirectional text/byte streams. This is handy for real-time command/telemetry.
Connect and send a command:
const NUS_SERVICE = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';
const NUS_TX_CHAR = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'; // write
const NUS_RX_CHAR = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'; // notify
async function connectNUS() {
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: [NUS_SERVICE] }],
});
const server = await device.gatt.connect();
const service = await server.getPrimaryService(NUS_SERVICE);
const tx = await service.getCharacteristic(NUS_TX_CHAR);
const rx = await service.getCharacteristic(NUS_RX_CHAR);
await rx.startNotifications();
rx.addEventListener('characteristicvaluechanged', e => {
const msg = new TextDecoder().decode(e.target.value);
console.log('Received:', msg);
});
// Send "Hello"
const data = new TextEncoder().encode('Hello\n');
await tx.writeValue(data);
}
This pattern gives you a simple real-time command channel between browser and device.
Building a real-time dashboard
For a production-like dashboard:
- Create a single BLE connection that accepts notifications.
- Push incoming data into an in-memory buffer or observable stream (e.g., RxJS) for debouncing and transformation.
- Render charts (Chart.js, D3, etc.) and update them in response to notifications.
- Optionally relay data to the server via WebSocket/SSE for long-term storage or multi-client sharing.
Example architecture: Device (BLE) <-> Browser (Web Bluetooth) <-> WebSocket server <-> Other clients.
To forward BLE data to a server in real time:
const ws = new WebSocket('wss://your-server.example/telemetry');
function handleNotifications(event) {
const value = parseSensorValue(event.target.value); // Implement parsing
updateLocalUI(value);
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ time: Date.now(), value }));
}
}
This is useful when multiple users should see the live data or you want to persist telemetry.
Best practices and production tips
- HTTPS is required: browsers allow Web Bluetooth only on secure contexts (except localhost).
- User gesture: calling
navigator.bluetooth.requestDevice()
must be triggered by a user action (click/tap). - Use filters: specify services or name prefixes to avoid overwhelming the user with unrelated devices.
- Limit optionalServices: request only the services you need to reduce permission surface.
- Handle disconnects gracefully: listen for
gattserverdisconnected
and attempt reconnection with exponential backoff. - Throttle updates: devices can emit very fast; batch or sample values to avoid UI/performance issues.
- Respect battery: avoid continuous polling; prefer notifications.
- Security and privacy: BLE data is delivered to the browser; avoid exposing sensitive device IDs. Use application-level authentication when forwarding to servers.
- Fallbacks: if Web Bluetooth isn’t available, provide instructions or a native fallback app.
- Pairing vs bonding: bonding (persistent pairing) behavior depends on the device and OS. Treat it as platform-managed.
Debugging and tools
- Use the browser console to inspect devices and errors.
- Chrome’s chrome://bluetooth-internals/ and system Bluetooth logs help diagnose GATT issues.
- Increase BLE logging on your device (if supported) and test with desktop and Android browsers.
Advanced patterns
- Web Workers: parse and pre-process telemetry off the main thread for heavy workloads.
- Observables: RxJS or similar libraries make composing streams of notifications easy (debounce, sampleTime, buffer).
- Multiplexing devices: manage multiple simultaneous connections and merge their streams into a unified dashboard.
- Firmware updates: OTA DFU flows often require special BLE procedures and chunked writes-follow vendor docs.
Security, permissions, and user experience
- Always inform users what data is accessed and why.
- Minimize permission prompts by requesting access only when needed.
- Provide clear UI for device selection, connected status, and reconnection steps.
Example project ideas to try
- Real-time environmental monitor: multiple BLE sensors push temp/humidity to a browser dashboard; forward to a server for historical graphs.
- Remote control panel for a robot: use NUS or custom service for commands and telemetry.
- BLE-based presence detection: monitor RSSI from devices and estimate proximity for location-aware web apps.
Compatibility and standards
Web Bluetooth is an evolving web capability. Check browser support and the latest spec:
- MDN Web Bluetooth API: https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API
- Web Bluetooth community group and spec: https://webbluetoothcg.github.io/web-bluetooth/
- Google guide and samples: https://developer.chrome.com/articles/bluetooth-web/
Conclusion
The Web Bluetooth API opens browsers to a wide range of real-time IoT scenarios-sensor dashboards, device configuration UIs, and bi-directional control systems-without installing native apps. Understanding GATT, using notifications, respecting security constraints, and applying robust reconnection and data-handling patterns are the keys to building reliable real-time applications.
For hands-on experimentation, try connecting to sample BLE devices (or a microcontroller running a small BLE sketch) and iterate: start with notifications, add bidirectional communication, then layer on visualization and server-side forwarding.