OhAlam, knowing the air is safe
Live at: app.oh-alam.my
Source: alam-api · alam-dashboard
How it started
Haze from fires across the region used to drift over Malaysia, and earthquake tremors in the region kept coming up in the news. Every time that happened, I found myself hopping between several websites just to answer one simple question: is it safe to go out right now? Air quality sat on one government site, the weather on another, the earthquake feed on a third, the flood warnings on a fourth. I kept wishing there was a single calm answer, something I could open on any phone, tablet, or computer. That is why I built OhAlam.
What it does
It is a website at app.oh-alam.my. Pick a town and it shows you, in one place:
- the current air quality, and how risky that band is
- the weather now and for the next few days
- any earthquakes, flood alerts, or official warnings nearby
- news worth knowing about
It works in English or Bahasa Malaysia, in light or dark mode, and stays usable offline after your first visit. When you want to warn your family about haze or a flood, there is a one-tap share card, a tidy image you can send in a chat.
Where the data comes from
I create none of it. OhAlam gathers pieces that public services already publish, cleans them into one consistent shape, and serves them from one place:
| What | Source |
|---|---|
| Air quality | Malaysia's own monitoring (DOE / APIMS) [1] |
| Weather and forecast | Open-Meteo [2] |
| Earthquakes | USGS [3] |
| Official forecasts and warnings | MET Malaysia [4] |
| Flood and rain alerts | InfoBanjir [5] |
| El Niño and La Niña phase | NOAA [6] |
| Hazard news | Malaysian news feeds |
How it stays fresh and free
A quiet background job checks the sources every few minutes and saves anything new. It follows two simple rules: it only adds, and it never writes the same reading twice. Adding keeps a history you can look back on; not repeating keeps the running cost at zero, because the whole thing lives on free tiers (Cloudflare for the serverless code, GitHub Actions for the timer). No server of mine, nothing hosted on my computer.
How a town loads
The screen asks for one small summary for the town you are looking at, not the whole country. In one line, the data moves like this:
public sources → my tidy database → one simple API → your browser → you
A small rule keeps it honest: latest wins. If a slow, old reply arrives late, it can never overwrite a newer one, so you always see the freshest reading.
A quick tour of the app
The app has five screens, opened from a bar at the bottom: Home, Weather, Air, Hazards, News. On any of them you can switch your view between three scopes: the town nearest to you, your whole state, or all of Malaysia. Together they answer one question: is my town okay right now.
Every screen draws from the same one town summary, and only fetches a little extra when you ask for it (for example the air-quality trend for a station, or the flood list). It never downloads the whole country at once.
- Home. The at a glance screen. Your town's air quality and current weather, the day's high and low, when rain might come, plus the latest warnings, earthquakes, and a live flood count. One opening screen instead of five.
- Air (AQI). The heart of the app. A colour-coded AQI scale with a count for each band, a map of every station coloured by its air band, a filter to show only the bad bands, and a trend chart of the last 24 hours for any station. Each band comes with simple advice on what to do.
- Weather. Current conditions and a short forecast for towns, shown on a map with temperature-coloured markers. It combines the official MET forecast with Open-Meteo details.
- Hazards. Two tabs: Flood (river level and heavy rain alerts) and Earthquakes (the recent tremor feed), alongside the active official warnings. Severity is colour-coded so you can scan for danger fast.
- News. A tidy, searchable list of recent Malaysian hazard news, showing the outlet and how long ago it was published, opening each story in its original place.
Why I built it this way
- Free and serverless. Nothing to rent, nothing to pay, and it absorbs a rush of users on its own.
- The website never talks to the sources directly. When a source changes, I fix one place, not many.
- A database holds the last good data. The app stays useful even if a feed goes down.
- One request per town. Fast and cheap, instead of downloading the whole country.
- Latest wins. You always see the newest reading.
- Any device, mobile first. People check on the go, but the same page opens fine on a computer.
- Two languages and dark mode. Comfortable for the most people.
- A small chart by hand, not a chart library. Loads faster, which I value more than fancy charting.
Building it, some honest notes
A lot of the work was the boring kind: keeping a free service free, and keeping the data good. A few notes for anyone building something similar.
Staying under the free database write limit
The cloud database (D1) that stores what I gather comes with a free daily cap of roughly 100,000 row writes. The first time I ran a full data load, it burned a whole day's budget at once. Three habits keep it well under the cap:
- Only fetch what is old. Each source remembers when it was last checked, so a run polls only the sources that have gone stale, not everything blindly.
- Slow the busy feeds down. Air quality and flood data are checked every 15 minutes, weather hourly, instead of constantly.
- Write lean. A newer reading replaces an older one for the same spot at the same moment, and old history gets pruned, so the store stays small.
Populating data without touching the limit twice
The loading job runs on GitHub Actions, not inside the database, so the heavy work never counts against the database's CPU or write budget. It sends the store data in small, repeatable chunks, and a chunk is skipped if it already exists. A re-run therefore never double-counts writes.
Refreshing data on the screen
The browser is the last cache in the chain, and the easiest to get wrong:
- The page file is never cached, so the browser always asks for the newest version.
- The built asset files (their names include a hash) are cached as immutable, so they only re-download when they actually change.
- The service worker (what makes it work offline) refreshes immediately, so it never pins a visitor to an old version.
- On the phone, latest wins means a slow old reply can never cover up a newer one.
Getting the data good in the first place
- Every source is cleaned into one common shape before it is stored, so the app never has to know which website a value came from.
- A value is recorded once for its (source, station, time) combination, so there are no accidental duplicate entries.
- Different air-quality scales are kept apart. Malaysia's own AQI scale is the one the app uses for its advice; a US-style number is never mixed into it.
- The app always reads from stored data, never straight from a live feed, and a small drift-check compares the town registry against the source on a schedule, so a silent upstream change gets caught.
A few simple words
- Source: a public service that publishes a piece of the data.
- Database: a calm, well-organised place that stores what I gather.
- API: the one door the website asks for data; other programs can use it too.
Sources
- DOE APIMS air quality, https://apims.doe.gov.my
- Open-Meteo, https://open-meteo.com
- USGS earthquakes, https://earthquake.usgs.gov
- MET Malaysia, https://www.met.gov.my
- InfoBanjir, https://publicinfobanjir.water.gov.my
- NOAA El Niño / La Niña, https://www.cpc.ncep.noaa.gov/products/analysis_monitoring/ensostuff/ONI_v5.php
The project lives in the alam-api and alam-dashboard repositories linked at the top of
this note.