TECHNOLOGY

Building Scalable Platforms with Modern Technologies

MM Mr MehediFounder & CEO, Cubie Technologies · · 5 min read

Every product we run sits on roughly the same foundation. That was not the plan at the start — it is what happened after enough painful redeploys taught us which decisions were worth standardising.

The stack

  • Node.js and Express for application servers
  • MySQL as the primary database
  • Flutter for mobile
  • Cloudflare R2 for object storage
  • GitHub to Hostinger for deployment

None of that is exotic. That is the point. Every one of these has excellent documentation, a large community, and predictable failure modes. When something breaks at 2am, boring technology is a feature.

Three lessons that cost us time

1. Local file storage does not survive deployment

ChatCubie originally stored uploaded images on the server's local disk with multer. It worked perfectly — until the first redeploy, when the host rebuilt the directory and every uploaded file vanished.

The fix was moving to Cloudflare R2. The lesson was broader: on managed hosting, treat the filesystem as temporary. Anything that needs to outlive a deploy belongs in object storage or a database.

2. SQLite is a great start and a bad finish

The first version used SQLite. It is genuinely excellent for getting moving. But once you have concurrent writers and a hosting setup where the process can be restarted underneath you, its assumptions stop matching reality.

Migrating to MySQL meant converting roughly two hundred database calls to async/await. Doing it earlier would have taken an afternoon. Doing it later took a week.

3. Store timestamps in UTC. Always.

The economic calendar in Cubie FX had a bug where events showed at the wrong hour for some users. The cause was mixing server-local time with UTC in different code paths.

The rule that fixed it and has not caused a problem since: store UTC, convert at the moment of display, never in between.

Security is a default, not a phase

The things that are cheap to do from day one and expensive to retrofit:

  1. Parameterised queries everywhere — no string concatenation into SQL, ever
  2. Password hashing with bcrypt at a real work factor
  3. Timing-safe comparison on login so response time does not leak whether an account exists
  4. Every user-scoped query filtered by user ID at the query level
  5. Sanitising anything rendered as HTML, including link protocols

The last one is easy to miss. A markdown renderer that allows arbitrary URL schemes will happily render a javascript: link, which is a cross-site scripting hole hiding in a feature nobody thinks of as risky.

What scaling actually looked like

Not sharding or microservices. It looked like: fix the N+1 queries, add indexes to the columns you filter on, cache the responses that do not change, compress images before upload rather than after, and stop re-fetching data the client already has.

Most performance work is undoing something unnecessary, not adding something clever.