PostgreSQL vs MySQL: When I Reach for Each

I get asked this a lot by people just starting out freelancing: “PostgreSQL or MySQL for a new project?” The honest answer isn’t as simple as they’re hoping for — I’ve used both for years now, and I don’t have one answer because there genuinely isn’t one.
The case that sticks with me most: an internal dashboard for a client that I originally built on MySQL because the hosting they already had was MySQL-only — old shared hosting, not a VPS. A few months in, they asked for a reporting feature that needed nested JSON to store a dynamic per-user form config. MySQL can handle JSON, but the queries got a lot messier than they would’ve been with Postgres’s JSONB, which has indexing and query operators that are just more comfortable to work with. I didn’t migrate the database — too risky for a system already in production — but it was a lesson: if I’d already suspected I’d need flexible data structures from the start, I should’ve started on Postgres.
When I reach for PostgreSQL
- Richer data types — native JSON/JSONB with indexing that’s actually fast, arrays, custom types
- Queries that are going to get complex, lots of joins and aggregation that need window functions or CTEs
- Built-in full-text search without bolting on Elasticsearch or a separate tool just for simple search
- The project’s going to deploy on Supabase or a platform that’s PostgreSQL-native from the start
When I reach for MySQL
- The target hosting/environment is genuinely cheaper or more familiar for MySQL — this still happens a lot on smaller client projects with legacy shared hosting
- Whoever maintains it long-term is more comfortable with MySQL, and I won’t be the one holding the project forever
- The needs are straightforward — standard CRUD, simple reports, nothing genuinely gnarly
On raw performance, I rarely find a difference that actually matters for the small-to-medium apps I work on — correct indexing and sensible schema design decide a lot more than which engine you picked. I’ve seen MySQL queries crawl not because MySQL is bad, but because there was no index at all on the filtered column. Switching to Postgres wouldn’t have fixed that.
If I have to pick one default for a new project with no special constraints from the client, I lean PostgreSQL these days. Not because MySQL is bad — I still run it on a few older projects that are already there — but because the data-type flexibility means I don’t have to rethink the foundation when requirements shift midway, and client requirements almost always shift midway.