Day 2: Our tech stack decision: why Kotlin/Spring Boot + React + Postgres
Day 2 of 30. Zero lines of code yesterday, but today we're laying the foundation with our tech stack choices.
Day 2 of 30. Zero lines of code yesterday, but today we’re laying the foundation.
Picking a tech stack for a new project is quite an important decision. It’s easy to spend days researching, comparing benchmarks, reading opinions and all of that. However, while those things might be important, we’re not doing that for this project. Our main driver is to go fast, which means we’re going to pick something we know well. In this post we will share what we chose and why, something we decided in about an hour but which was based on many years of experience. We are however aware that new insights may lead to new solutions, so things might change a bit while we develop our platform.
The backend: Kotlin with Spring Boot
We love Kotlin, and we use Kotlin on the backend. Combined with Spring Boot, and in the future perhaps other frameworks, this is a solid foundation for our app. Please keep in mind that while this might work for us, your experience might be very different. Our advice is to pick something you’re comfortable with, and not obsess over technology too much at this stage.
[✓] Kotlin + Spring Boot
CHOSEN
▼
[+] Pros
- + Years of team experience means fast development
- + Spring Boot has solved most problems we'll encounter
- + JVM handles concurrency extremely well
- + Kotlin coroutines for async when needed
- + Excellent LLM support for boilerplate generation
[-] Cons
- - Higher memory footprint than Go
- - Requires at least 1GB RAM
- - Slower cold starts than some alternatives
The best framework is the one where you can ship features without constantly checking documentation.
[ ] Go
▼
[+] Pros
- + Lower memory footprint
- + Fast cold starts
- + Great for concurrent workloads
- + Simple deployment (single binary)
[-] Cons
- - Team has less experience
- - Smaller ecosystem for web frameworks
- - Would slow us down initially
[ ] Node.js / TypeScript
▼
[+] Pros
- + Same language as frontend
- + Large ecosystem
- + Good async performance
[-] Cons
- - Less familiar for our team
- - Callback/promise complexity
- - Type safety less mature than JVM
Our main reasons for picking Kotlin on the backend:
We’re fast in it. We have years of great experience with Spring Boot which matters more than anything else. The “best” framework is the one where you can ship features without constantly checking documentation. Also, LLM models are really good for the boring parts of the code, like generating the boilerplate, which makes us move just a bit faster.
It’s boring in a good way. Spring Boot has solved most problems we’ll encounter. Authentication, scheduling, database access, API validation, testing: all of these functionalities are included. There are well-documented way to do all of it, and most problems have been solved before.
The JVM is actually great for this workload. We’ll be running headless browsers, managing job queues and handling concurrent API requests. The JVM handles concurrency extremely well, and Kotlin’s coroutines give us async capabilities when we need them.
The frontend: React
[✓] React + Vite + Tailwind
CHOSEN
▼
[+] Pros
- + Team knows it well from previous projects
- + Mature ecosystem with lots of plugins
- + Excellent TypeScript support
- + Vite provides fast builds
- + Tailwind enables rapid UI development
[-] Cons
- - Larger bundle size than some alternatives
- - Can be complex for simple apps
For a dashboard with a few pages, anything modern works. We picked what we're productive in.
[ ] Vue.js
▼
[+] Pros
- + Simpler learning curve
- + Good documentation
- + Single-file components
[-] Cons
- - Less team experience
- - Smaller ecosystem than React
[ ] Svelte
▼
[+] Pros
- + Smaller bundle sizes
- + Less boilerplate
- + Compile-time optimizations
[-] Cons
- - Newer ecosystem
- - Team would need to learn it
- - Fewer job candidates know it
We’re keeping the frontend minimal. We’re build a landing page, a screenshot demo page, a login flow, several dashboard pages showing API usage and managing keys, and for our advanced users an audit log and several enterprise features. Our initial design covers around 10 pages in total.
The database: Postgres
This one was easy, and we had zero hesitation in choosing Postgres. Postgres is the right choice for almost any scenario in which you need a database.
[✓] PostgreSQL
CHOSEN
▼
[+] Pros
- + Rock solid reliability
- + Great tooling and hosting options
- + JSON columns for flexible data
- + Can handle queues, full-text search, and more
- + Excellent performance for our scale
[-] Cons
- - Requires more ops than managed alternatives
- - Not horizontally scalable out of the box
Postgres can do far more than most developers realize. Just use Postgres.
[ ] PlanetScale / Neon (Serverless)
▼
[+] Pros
- + Zero ops required
- + Automatic scaling
- + Pay per use
[-] Cons
- - Cold starts and latency concerns
- - Connection pooling complexity
- - Potential for surprise costs
[ ] MongoDB
▼
[+] Pros
- + Flexible schema
- + Good for document storage
[-] Cons
- - Postgres JSON columns cover our needs
- - Another tool to manage
- - Weaker ACID guarantees
The stuff we’re not using
Worth mentioning what we deliberately skipped:
- Kubernetes - Great when you have a big team, but at the moment, it’s overkill for us. A single VPS or two will handle our load for a long time.
- Microservices - While we appreciate the concept of microservices, we’re aiming to keep things simple, so we’re going for a modular Monolith, until there’s really a compelling reason to switch to a different architecture.
- GraphQL - REST is simpler for this use case, and we have a well-defined API, so at the moment we think GraphQL would be overkill.
- NoSQL - We see no benefit over Postgres for our data model, plus if we need to, Postgres is a pretty good NoSQL database.
- Serverless functions - Headless browsers and serverless are not always a great combination due to the startup time, though we could keep some in a warm state. We’re aiming for performance, and things like cold starts isn’t great for our latency requirements. Besides that, a lot of serverless functions have a maximum duration, which could potentially be a mismatch for our use case.
What we set up today
- Created the GitHub repository (monorepo:
/api,/web,/infra) - Initialized the Spring Boot project with Kotlin, added basic dependencies
- Initialized the React project with Vite and Tailwind
- Set up a basic GitHub Actions workflow: build and test on every push
- Wrote a placeholder README documenting our stack choices
We don’t have a running app yet besides our basic starter project. But the skeleton is there, our CI is green (for now…), and tomorrow we can start writing actual features.
Tomorrow: architecture sketch
On day 3 we’ll draw out how the pieces will fit together. How does a screenshot request flow through the system? Where do the images go? What happens when things fail?
Book of the day
As before, we have a small book recommendation. As we mentioned before, we use Postgres for everything, and we often say “just use Postgres”, and you’ll be fine. Well, it seems Joel and Aaron agree with us, and they wrote a book about it with the great title “Just Use Postgres”.
Just Use Postgres by Joel Clermont & Aaron Saray
This book landed at the perfect time. The core argument: Postgres can do far more than most developers realize, and reaching for additional tools often adds complexity without real benefit.
If you’re defaulting to “Postgres for data, Redis for queues, Elasticsearch for search” without questioning it and want to benefit from some simplification, this book is worth a read.