Lessons from Building BoCharge
A reflection on building a small public map as a side project, where agents helped with speed but the real learning came from data management, product judgment, and the building open source.
Motivation: the problem I felt as a new EV owner
The day I started building BoCharge, I was just annoyed at a problem I was facing as a new EV owner. You see, I live in Singapore, which is a very densely populated country, well-mapped out on public maps, with very modern EV and public transport architecture. Since EVs were introduced to Singapore, the number of EVs has been rapidly increasing, but the experience of finding one still feels scattered across provider apps, map searches, carpark listings and word of mouth.
The user journey was very strange. I had to search for an EV charger from Google Maps or from different EV charger provider apps. If I came from Google Maps, I will need to ensure that I have the right app installed for that particular provider, install it and set it up, and then actually get my car charged. As a new car owner, I didn't always have all the apps available, so this was a lot of work. Even though I gradually installed all the apps I needed to install, it was annoying having to switch between all of them all the time. And sometimes, even after finding the right charging station, I would realize that there are no charging spots available for me which is definitely very frustrating.
This got me thinking, especially as someone who used to spend a lot of time of data crunching as a data engineer - there has to be a better way of consolidating all this data, putting it into a single application or single map and actually serving all this data in real time. With all projects which are data-centric, you need a data source. Assuming this data source is available, you'll want to know information on:
- whether a charger is available
- whether it's worth driving towards
- which provider app you need when you get there ... to plan all these in your route
I also wanted to test something I have been thinking about as a developer: how much can agents really help when the goal is not just to generate code, but to make a data-centric tool useful? I've been building agents and vibe coding for a while, but all these advancements in AI capabilities in coding actually came after I made a small career pivot from data science and engineering to AI agent engineering. So I never actually tried doing a lot of data crunching using AI tools. This became a side project with a very real learning loop. I came in with years of data engineering instincts, but I still had to learn the quirks of this specific data feed, this specific user problem, and this specific product.
How much of this was vibe-coded?
To get the elephant out of the room, a common question which people kept asking me is how much of this was vibe coded? Well, my answer is - a meaningful amount (about 80%), but not everything. The UI scaffold, route wiring, styling iterations, helper functions, and parts of the data normalization layer moved much faster because I could lean on Codex and Claude. This was definitely super helpful because my background in engineering has mostly been in data and backend work rather than frontend, so I'm actually quite bad at building frontends.
But I would not describe the whole thing as just vibe-coded. The important decisions were still human-led: what problem was worth solving, which source should be trusted, what assumptions were acceptable, how to cache a data feed responsibly, and which weird edge cases were real enough to encode in the product. Agents were good at accelerating well-scoped implementation work. They were not good at caring whether the result made sense to a driver in Singapore, and noticing small quirks in the data.
Two particular things to bring up:
- Designing how the UI/UX can be easily digested by a driver, probably checking the web app on their phone on mobile while on the go. To address this, I had to ensure that the web app is mobile-friendly and easily readable even on phone screens and easily navigated with just a few fingers.
- I had to intervene manually in the data wrangling. One thing I learned from my years of data engineering experience is to basically develop an intuition over dealing with messy data sources. The AI-generated scaffold struggled to correctly map provider information (such as SP, Shell, and others) to the appropriate filters, because the data was quite nested. Presumably, the models didn't actually have the intuition to un-wrangle the data in an easily digestible way, and it kept telling me that it couldn't provide provider information. This made me very frustrated, because that was exactly the information I was trying to simplify as part of this app, to ensure that people know which app to get directed to when they have to. Thankfully, when I checked the data myself, I found out that this is actually possible. Well, it's good to know that my technical skills can't 100% be replaced by agents just yet!
Architecture & Tech Stack
The architecture is intentionally simple. I'm primarily an AI and data engineer rather than a full-stack engineer, and I also relied heavily on AI tools to help make many of the architectural decisions.
At its core, the application consists of a React-based map frontend sitting on top of a lightweight Express server. Charger data comes from LTA DataMall, with server-side caching configured to match the refresh cadence of the source feed.
The main data source for BoCharge is LTA DataMall’s EV charging feed, which provides charger locations and live availability. For the map and place search layer, I chose OneMap and Leaflet instead of Google Maps.
Google Maps was definitely an option, but it also introduces usage-based pricing for map loads and related services. For a Singapore-focused personal project, OneMap and Leaflet were a more cost-conscious and practical choice.
The whole thing runs as one small Node service: Express serves the API and the built Vite frontend, while the LTA key stays server-side.
How I used LTA data and where data quality got interesting
The LTA feed is the backbone of the data in this app. The app calls the EVCBatch endpoint, receives a temporary batch download link, downloads the JSON payload, normalizes the nested station records, and caches the result on the server.
On paper, that sounds straightforward. In practice, it required some data intuition and manual intervention. The feed gives latitude, longitude, charger statuses, plug information, and enough station metadata to build a real map. The tricky part is that the details users care about were quite nested or hidden.
Station records can contain nested charging points, plug types, connector identifiers, and status fields. Availability has to be derived from those connector statuses when they exist. Operator names can also appear at different levels of the record, so a normalizer that only looks at the station-level object may miss provider information that is present inside a nested charging point. That is the kind of bug that looks like missing data from the outside, even when the payload already contains the answer.
This was one of the clearest human-in-the-loop moments and reminded me of how data engineering often is in reality - not glamorous, and where the devil is sometimes in the details.
Availability and filter decisions
One of the biggest questions in my head when working on this was really just about how to best design filters and search. The map should answer the question top of mind for each driver: "Can I charge here now?"
That led to a few opinionated design decisions. The default experience prefers chargers with available connectors because that is the highest-intent use case. Fast charging is provided as a simple toggle, because the point is to help someone make a quick decision. Operator filters matter because charging is not only about location; drivers often need the right app or might have preferred operators before the charger is actually usable.
These days, the instinctive answer to “make search smarter” is often to use an LLM. That was tempting, especially because users do not search in clean database terms. They type things like “near Marina Bay,” “mbs,” or “chargers around Tampines Mall,” and expect the app to understand the intent.
But BoCharge is an open-source project, so I wanted the search to stay simple, cheap, and easy for anyone to run. Adding an LLM would mean API keys, ongoing costs, latency, failure modes, and more infrastructure for a problem that could mostly be solved with deterministic logic.
So instead of using an LLM, we normalize the query, removes filler words like “near” and “around,” scores charger records against station names, addresses, postal codes, operators, and plug types, and supports common aliases like “mbs.” If the query is clearly asking for a place rather than an exact charger name, the app turns that into a coordinate and ranks chargers by distance. Only when the place is not already known does the server call OneMap as a fallback geocoder, cache the result, and return just the location data needed by the frontend.
It's not fancy tech but it works - it's predictable, explainable, low-cost, and good enough for the way people actually search for nearby chargers.
Costs
One practical thing this project reminded me of is that shipping a public open-source tool is not free, even when agents make the coding loop much faster. It is easy to romanticize open source as pure sharing, and I do believe in that spirit, but the infrastructure around a live project is not automatically sponsored. Hosting, domains, monitoring, usage spikes, maintenance, and the small services that make a project feel real all add up.
For BoCharge, I paid for the web hosting myself via my own railway account, but was glad to have the domain covered by Quan You (@cquanu) who reached out to me and offered to pay for a .com domain. Love this builder community spirit in Singapore!
What I would improve next
One of the first things I did after building this was to start spreading the word - I used my personal social media accounts as well as various EV charger social groups in Singapore, and also share it with my friends who I know own EVs. And I collected lots of great feedback! Special thanks to my friends who knew how obsessed I get with my personal projects and perfecting them, and also shared the BoCharge url around with their connections to get more feedback.
Here are some improvements I've planned in the pipeline:
- Linking this up with car park availability data
- Allow users to plan routes around their EV charger location
- better provider app deeplinking
- more UI/UX improvements
- perhaps moving this to a cheaper AWS hosting
This originally started as a solo project for me to experiment on some stuff, but I've already had one contributor who created PRs and a few friends who just provided advice here and there.
I would love to have anyone contribute to this! Feel free to fork and create a PR to this repo or just DM me on X or LinkedIn if you have any questions.
Thank you's
To wrap up this reflective post - and also my first on my personal site (!) - I want to express a huge thanks to my domain sponsor, and family and friends who circulated this around, shared it with people who would have useful reactions, and helped me get early feedback before publishing. This was originally a solo project, but I had a lot of fun and talked to so many people about it, got to dabble in my personal interests in data engineering and product design - that I really enjoyed it! Perhaps I should start building more things not for work and for open source in future.
Also, it took me some motivation and courage to write about my personal projects in the public space, even though I've done this for work for a very long time. Special thanks to family, friends, colleagues and ex-colleagues who encouraged me to do what I like and enjoy the ride!
Thanks for reading and do check out the following links!