When I set out to build DirectCall, simplicity wasn’t just a goal - it was the foundation - I’m the only developer.
Here’s how I built it.
The Stack
Node.js + Express handles everything from the REST API to WebSocket signaling. It’s all in a single container - no microservices drama, just one server doing its job.
The frontend is pure vanilla JavaScript (ES6+), HTML5, and CSS. No React, no Vue - maybe later when/is needed.
WebRTC does the heavy lifting for peer-to-peer video. Browsers talk directly to each other; the server just coordinates the connection. Google’s public STUN servers handle NAT traversal, so the calls work behind firewalls and routers.
PostgreSQL stores user data, sessions, and analytics on DigitalOcean’s managed platform. Reliable, managed backups, no database administration headaches.
The whole thing runs on DigitalOcean, containerized with Docker, deployed via GitHub Actions. Every push to main becomes a production release. SSL from Let’s Encrypt, JWT for auth.
How I Built It
Development followed a simple rule: if it won’t ship faster, skip it. I’m a solo developer who’d just been laid off. Time wasn’t a resource I had in abundance.
What I Skipped (And Why)
Tests: Zero automated tests. Not proud of it, but honest about it. When you’re trying to solve a problem that matters right now, “what if” scenarios can wait. I manually tested every feature and kept deployment simple enough that when something broke, I could fix it fast.
Staging Environment: Production test mode only. Set up ngrok for local webhook testing? Skipped. Dedicated staging server? Nope. Direct to production with test credentials. Risky? Sure. But I had zero users and needed to move.
TypeScript: Could have given me types and better tooling. Also adds build steps and complexity. Went with pure JavaScript. It works.
Frameworks: No React, no Vue, no Angular. Why? I expected the app to be super simple - and it is. If I’ll need anything else I’ll think about it.
What I Kept (And Why)
Docker: Development, production, same container. No “it works on my machine” because my machine is the same everywhere.
CI/CD: GitHub Actions that deploy on every push. No approval gates, no manual steps. Push to main, code ships. If it breaks, I fix it. Same-day deploys keep me honest.
Single Container: Frontend and backend together. Microservices make sense at scale. But scaling is a problem for future-me. Present-me needs to ship.
Merchant of Record (MoR): Chose MoR instead of just a payment gateway. It handles taxes, compliance, and legal requirements - things I don’t have time to learn as a solo developer. Costs more, but worth it.
Why This Matters
I could have picked the trendier stack…
This stack lets me focus on what actually matters: the connection between two people when they need it most. It’s fast to deploy, easy to debug, and cheap to run. Most importantly, it stays out of the way.
The corners I cut? They’re all documented. I have a TECH_DEBT.md file that tracks what I skipped and when I plan to fix it. But that’s a luxury for when the app pays for itself.
For now, every line of code serves one purpose: getting two people connected when nothing else works.
Just like it should.
You may also find these posts interesting: