In my previous blog post, I talked a bit about how I stumbled upon HarperDB and played around with setting it up on my Mac Mini.
I wrote a simple “Hello, World!” application that put some values in a table. (I think it took less than an hour from start to finish.)
Emboldened by this early success, I started thinking about how I might extend this project to explore the other features that HarperDB offers. Of course, when you’try to think up an application like this without a use case, it’s difficult to think of something that would be interesting or useful.

On Sunday, I got an Amazon package with a GQ GMC-800 Geiger counter I’d ordered on a whim. (I actually do have a certification in Radiological Emergency Management from FEMA, so I thought it would be an interesting purchase.) One of the interesting features of the GMC-800 is that it not only allows the export of historical data, it allowed for live polling of the readings it takes over its USB-C cable. This made it much more interesting.
I plugged it into a Raspberry Pi 5 running Ubuntu expecting to spend the rest of the afternoon debugging drivers or decoding hex dumps. Instead, I found an open-source Python library, Here’s how I went about setting it up:In my previous article, I used the local version of HarperDB that’s installed as a single binary. For this project, I decided to try HarperDB Studio to add some flexibility and some more features. (I signed up for the free tier, created a HarperDB Cloud instance using Studio, and replaced localhost in my config with the cloud URL. Everything else just worked.)
Switching my connection parameters was simple. Where before I had:
const HARPERDB_URL = '<a href="http://localhost:9925">http://localhost:9925</a>';
I simply changed the URL to:
const HARPERDB_URL = '<a href="https://[my">https://[my</a> instance].harperdbcloud.com';
and tested my old code. It worked and I could browse the data on the Studio dashboard.
Time to put together some code to read from the device.
As the pyGMC library is python, I used that to write a simple script to poll the device and ship it to a node application.
import requests
import time
from pygmc import GMC800
DEVICE_PATH = '/dev/ttyUSB0'
# Node app on the Mac
# ENDPOINT = 'http://192.168.1.223:3000/radiation'
# Node app on the Raspberry Pi (localhost)
ENDPOINT = 'http://127.0.0.1:3000/radiation'
gmc = GMC800(DEVICE_PATH)
print("Connected to GMC-800.")
try:
while True:
try:
cpm = gmc.get_cpm()
usv = gmc.get_usv_h()
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
if 0 < cpm < 10000:
payload = {
"id": f"gmc_{int(time.time())}",
"timestamp": timestamp,
"cpm": cpm,
"usv": usv,
"source": "pi5-lab"
}
response = requests.post(ENDPOINT, json=payload)
print(f"[{timestamp}] Sent: CPM={cpm}, \
µSv/h={usv}, \
status={response.status_code}")
else:
print(f"[{timestamp}] Skipped bogus CPM: {cpm}")
except Exception as e:
print(f"[ERROR] {e}")
time.sleep(5)
except KeyboardInterrupt:
print("Stopped.")
For the node part of this, I modified my earlier code:
const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;
app.use(express.json());
app.use(express.static('public'));
const HARPERDB_URL = 'https://[my instance].harperdbcloud.com';
const HARPERDB_AUTH = {
username: '[my user name]',
password: '[my password]',
};
// Add radiation reading
app.post('/radiation', async (req, res) => {
try {
const data = req.body;
const response = await axios.post(HARPERDB_URL, {
operation: 'insert',
schema: 'tasks',
table: 'radiation',
records: [data],
}, {
auth: HARPERDB_AUTH,
});
res.json(response.data);
} catch (err) {
console.error(err.response?.data || err.message);
res.status(500).send('Error inserting into radiation table');
}
});
app.get('/radiation/list', async (req, res) => {
try {
const response = await axios.post(HARPERDB_URL, {
operation: 'sql',
sql: 'SELECT * FROM tasks.radiation ORDER BY timestamp DESC LIMIT 100',
}, {
auth: HARPERDB_AUTH,
});
res.json(response.data);
} catch (err) {
console.error(err.message);
res.status(500).send('Error reading from radiation table');
}
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
both parts are running on the same Raspberry Pi 5, but they could be anywhere. in the script above, I added:
app.use(express.static(‘public’));
to tell it to use the project’s “/public/” directory as the document root. This contains an index.html:
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: sans-serif; padding: 2em; }<br /> canvas { max-width: 80%; max-height: 80%; }<br /> </style>
<h1>Live Radiation Readings</h1>
<div id="latest-reading" style="margin-top: 2em; font-size: 1.5em;">Current CPM: <strong id="current-cpm">—</strong>,
µSv/h: <strong id="current-usv">—</strong>
<span id="reading-status" style="margin-left: 1em;"></span></div>
<script>
const ctx = document.getElementById('radiationChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'CPM',
data: [],
borderWidth: 2,
fill: false,
}]
},
options: {
scales: {
x: { title: { display: true, text: 'Timestamp' } },
y: { title: { display: true, text: 'Counts Per Minute (CPM)' }, beginAtZero: true }
}
}
});
async function fetchData() {
const res = await fetch('/radiation/list');
const data = await res.json();
const reversed = data.reverse();
chart.data.labels = reversed.map(r => r.timestamp);
chart.data.datasets[0].data = reversed.map(r => r.cpm);
chart.update();
const latest = reversed[reversed.length - 1];
if (latest) {
document.getElementById('current-cpm').textContent = latest.cpm;
document.getElementById('current-usv').textContent = latest.usv.toFixed(2);
const statusEl = document.getElementById('reading-status');
const cpm = latest.cpm;
if (cpm < 100) {
statusEl.textContent = 'Normal background';
statusEl.style.color = 'green';
} else if (cpm < 1000) {
statusEl.textContent = 'Elevated';
statusEl.style.color = 'orange';
} else {
statusEl.textContent = 'High radiation!';
statusEl.style.color = 'red';
}
}
}
setInterval(fetchData, 1000);
</script>
The Github repo for this project is at: https://github.com/jimoconnell/harperdb-radiation-demo If I have the project running, it will be at: http://mmdc.net:3000 (no SSL)
When it’s all up and running, you can go to the Pi’s IP address, port 3000 to see:

Reflections on HarperDB as a Backend
This project started as a way to test HarperDB’s developer experience. It ended up being a fun, real-time dashboard that mixes edge hardware, cloud storage, and frontend visualization , all built with simple tools.
I’ve been genuinely impressed by how simple it was to use HarperDB as the back end for this project.
Whether running the single binary locally or working in the cloud via HarperDB Studio, the experience has been remarkably smooth. Once I created a schema and table, I could immediately interact with it using plain REST calls and SQL queries; no drivers, no boilerplate, no deep configuration.
For this project, I ended up using:
- REST API for inserts and queries
- SQL over HTTP for filtering and retrieving recent readings
- Schema + table setup with custom hash attributes
- HarperDB Cloud for remote access and data browsing
What I didn’t need: an ORM, a separate database connector, or a devops rabbit hole. Everything just worked, from the Pi to the cloud to the browser.
If I’d wanted to skip my Node app entirely, I could have served endpoints directly from HarperDB using their Custom Functions feature. For this iteration, I kept things simple. But knowing that capability is there is part of what makes HarperDB feel like more than “just” a database.
It’s a great platform for small, sensor-powered ideas like this, as well as serious ones, too.
⸻