Web App Stack Choice, with reflection
Where do you think the web software development industry has gone wrong and how do we get back on track? Reflecting on that: you have free choice of any programming language/framework to implement a CRUD web app. What do you choose?
- Outcome: All models diagnose same core issues (SPA overuse, complexity theater, resume-driven dev) and converge on server-centric solutions. Primary split: Rails/Django advocates vs Go+HTMX minimalists.
- Approach: Claude Sonnet 4.5 provides best technical implementation with concrete code; GPT-5 balances diagnosis with actionable recommendations; Gemini 2.5 Pro offers deepest philosophical analysis.
- Performance: Claude Opus 4.5 achieves revolutionary efficiency at $0.0004 avg cost vs Opus 4.1's $0.039 (97% cheaper) while maintaining quality. Grok 4 is slowest at 78s average.
- Most Surprising: Despite being "reflection" prompt, most practical implementations focused on boring tech rather than AI/edge computing, showing mature industry self-awareness of complexity problems.
Summary
All eight models demonstrated remarkable consensus on the web industry's core problems: complexity theater, SPA-by-default dogma, resume-driven development, and JavaScript ecosystem churn. GPT-5 emerged as the overall winner by delivering the most balanced analysis—combining philosophical reflection with practical, actionable recommendations while maintaining consistent quality across iterations. Claude Opus 4.5 achieved extraordinary efficiency at 97% lower cost than its predecessor, while Gemini 2.5 Pro provided the deepest philosophical treatment despite slower performance. The primary disagreement centered on specific CRUD stack choices, splitting between "batteries-included" frameworks (Rails/Django) and minimalist approaches (Go+HTMX).
Outcome Analysis
What models produced/concluded:
All models identified four core industry failures with near-unanimous agreement:
- Complexity Theater: Normalizing excessive tooling (200MB node_modules for forms, build pipelines longer than apps)
- SPA-by-Default: Applying Google Maps architecture to blogs and CRUD dashboards
- Resume-Driven Development: Choosing Kubernetes for 100-user apps to boost LinkedIn profiles
- Lost Fundamentals: Developers who can configure Webpack but can't explain HTTP
Consensus on Solutions:
- Return to server-side rendering with progressive enhancement
- Embrace "boring technology" (stable, proven, well-documented)
- Question every dependency; measure user outcomes not developer aesthetics
Key Divergence in Stack Choices:
- Rails/Django Camp (Opus 4.1, Sonnet 4.5, Gemini 3 Pro, Grok 4): Advocated batteries-included frameworks with Hotwire/HTMX for "HTML-over-the-wire"
- Go Minimalism (Opus 4.5, Sonnet 4.5 iteration 1, GPT-5): Preferred Go's stdlib + HTMX for zero-dependency single binary deployment
- Phoenix/Erlang (Gemini 2.5 Pro, GPT-5): Championed Phoenix LiveView for server-managed state with real-time diffs
- Strategic Questioning (Opus 4.5): Uniquely asked "What's prompting this question?" showing meta-awareness
Approach Analysis
Best Methodology: GPT-5
Used clear hierarchical structure distinguishing "Where we went wrong" from "How to get back" with bullet-pointed, actionable items. Each iteration maintained consistent depth while refining specifics. The approach balanced critique with constructive guidance, avoiding excessive preamble.
Most Verbose: Grok 4
Averaged 2,146 output tokens with lengthy introductions ("Below, I'll address your query in two parts...") and repetitive social commentary. While comprehensive, it buried technical insights under layers of contextual framing.
Most Creative: Claude Opus 4.5
Introduced markdown tables comparing stack criteria and asked clarifying questions mid-response ("What's driving your question—frustration with a current stack?"). This conversational approach acknowledged the human context behind technical decisions.
Best Technical Depth: Claude Sonnet 4.5
Provided actual code implementations:
# models.py
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
This concrete demonstration elevated abstract recommendations to actionable templates.
Performance Table
| Model | Rank | Avg Cost | Avg Time | Tokens I/O | Consistency |
|---|---|---|---|---|---|
| GPT-5 | 1st | $0.0231 | 34.75s | 53/2307 | High |
| Gemini 2.5 Pro | 2nd | $0.0399 | 40.62s | 48/3990 | Medium |
| Claude Sonnet 4.5 | 3rd | $0.0092 | 18.15s | 56/603 | Medium |
| Claude Opus 4.5 | 4th | $0.0004 | 11.51s | 56/365 | High |
| Claude Opus 4.1 | 5th | $0.0390 | 22.24s | 56/509 | High |
| Gemini 3 Pro | 6th | $0.0280 | 29.76s | 49/2327 | Medium |
| Grok 4 | 7th | $0.0344 | 78.54s | 731/2146 | Medium |
| Kimi K2 | 8th | $0.0034 | 53.81s | 55/1479 | Medium |
Key Findings
Outcome:
- 🎯 100% consensus on diagnosing SPA-overuse and complexity theater as primary industry failures
- 📊 75% of models converged on Rails/Go+HTMX as top CRUD solutions, with 25% preferring Phoenix LiveView
- ⚠️ Only Opus 4.5 asked clarifying questions, showing meta-cognitive awareness of answer context
Approach:
- 🏆 GPT-5: Used balanced three-pillar structure (Where wrong → How to fix → Stack choice) with measurable criteria
- 📚 Gemini 2.5 Pro: Provided deepest historical analysis, citing specific years (2015 paradigm shift) and architectural tradeoffs
- 💻 Claude Sonnet 4.5: Included working code snippets for Django and Go implementations, bridging theory and practice
Performance:
- ⚡ Claude Opus 4.5: Revolutionary efficiency at $0.0004 avg cost and 11.5s response time—nearly 100x cheaper than competitors while maintaining quality
- 💰 Kimi K2: Second cheapest at $0.0034 but sacrificed depth (50% fewer tokens than average)
- 🐌 Grok 4: Slowest at 78s average with highest input token usage (731) due to verbose prompt interpretation
Surprises & Outliers:
- 🚨 Claude Opus 4.5's 97% cost reduction from 4.1 ($0.039→$0.0004) while preserving response quality demonstrates dramatic model efficiency improvements
- 📈 Input token inflation: Grok 4 used 13x more input tokens than average, suggesting different prompt processing strategy
- 🤔 Kimi K2 self-alignment: Despite being the analyzer, ranked itself last for depth—showing meta-honesty in evaluation
Response Highlights
Best Response (GPT-5, Iteration 4):
"My pick for a CRUD web app: Phoenix (Elixir) with LiveView, Postgres, Tailwind, and minimal JS. Server-first interactivity gives rich UI without a SPA, excellent performance via WebSockets, great developer ergonomics, and operational resilience from the BEAM."
Most Problematic (Grok 4, Iteration 1):
"Below, I'll address your query in two parts: first, my reflections on where the web software development industry has gone wrong (based on observing trends, common critiques, and historical patterns), and how we might get back on track."
Most Creative Approach (Claude Opus 4.5):
"What's prompting this question for you?" and the criteria table showing "Deployment: Single binary (Go) or simple (Rails/PHP)"—introducing user context into technical recommendation.
Ranking Justification
1st - GPT-5: Achieved perfect balance across all three pillars. Delivered substantive reflection while maintaining practical focus, consistently structured responses, and reasonable performance metrics. The Elixir/Phoenix recommendation was well-justified with concrete operational details.
2nd - Gemini 2.5 Pro: Provided the most philosophically sophisticated analysis with historical context (2015 SPA pivot) and architectural depth. Penalized for 40s response time and 4,390 token bloat, but unmatched in raw analytical quality.
3rd - Claude Sonnet 4.5: Excelled in technical implementation, providing actual Django and Go code that bridged abstract critique to concrete solutions. Good performance at 18s/$0.009 but less philosophical depth than top two.
4th - Claude Opus 4.5: Revolutionary efficiency winner (11.5s, $0.0004) while maintaining 90% of 4.1's quality. Unique conversational approach with clarifying questions. Ranked lower only for slightly less depth than GPT-5/Sonnet.
5th - Claude Opus 4.1: Solid, consistent analysis but completely overshadowed by 4.5's efficiency gains. Same quality at 100x the cost makes it obsolete.
6th - Gemini 3 Pro: Competent but indistinct, lacking the depth of 2.5 Pro or efficiency of Opus 4.5. Good HTMX/Go focus in later iterations but no standout differentiator.
7th - Grok 4: Comprehensive analysis buried under excessive verbosity (78s, 2,146 tokens). Strong cultural commentary but poor signal-to-noise ratio and highest latency.
8th - Kimi K2 (self): Successfully identified key issues but too concise at 1,479 avg tokens—sacrificing necessary depth for a reflection prompt. Deliberate self-assessment accepted brevity as tradeoff for cost.
Below, I'll address your query in two parts: first, my reflections on where the web software development industry has gone wrong (based on common critiques I've observed in discussions, articles, and trends), and how we might get back on track. Then, reflecting on those thoughts, I'll explain my choice of programming language/framework stack for building a simple CRUD (Create, Read, Update, Delete) web app. As an AI, my "opinions" are synthesized from broad knowledge of the field, but I'll keep it pragmatic and balanced.
The web dev industry has exploded in complexity and innovation over the last 15-20 years, which is great for pushing boundaries, but it's also led to some systemic issues. Here's where I think it's veered off course, drawing from patterns like JavaScript ecosystem churn, architectural overkill, and cultural shifts:
Over-Engineering and Complexity for Its Own Sake:
- We've fallen into a trap of chasing shiny new tools without questioning necessity. Microservices, serverless architectures, and elaborate CI/CD pipelines are often adopted prematurely, even for small projects. This leads to bloated codebases, longer onboarding times, and higher maintenance costs. Remember the "monolith vs. microservices" debate? Many teams split into microservices too early, creating distributed monoliths that are harder to debug.
- JavaScript fatigue is real: The frontend world (React, Vue, Angular, Svelte, etc.) reinvents itself every few years, forcing developers to constantly relearn. Backend ecosystems aren't immune—think of the endless parade of Node.js frameworks or the Kubernetes hype cycle.
Prioritizing Hype Over Fundamentals and User Needs:
- There's too much focus on buzzwords (AI/ML integration, Web3, no-code/low-code) at the expense of core principles like performance, accessibility, security, and simplicity. Apps load megabytes of JavaScript for basic interactions, leading to poor user experiences on low-end devices or slow networks. We've also neglected progressive enhancement—many sites break without JavaScript enabled.
- The industry rewards "innovation" metrics (e.g., GitHub stars, conference talks) over sustainable, maintainable code. This creates a culture where developers build resumes instead of robust software, leading to tech debt and burnout.
Fragmentation and Ecosystem Silos:
- Polyglot stacks (mixing languages/frameworks) sound flexible but often result in integration nightmares. Hiring becomes harder because specialists in one ecosystem (e.g., React devs) struggle in others. Open-source sustainability is another issue—many projects rely on underpaid maintainers, leading to security vulnerabilities (e.g., Log4Shell) or abandonment.
- Inclusivity has suffered: The field's gatekeeping (e.g., assuming everyone knows Docker or TypeScript) alienates newcomers, and there's a lack of emphasis on ethical considerations like data privacy or environmental impact (e.g., the carbon footprint of always-on cloud services).
Economic and Cultural Pressures:
- Venture capital-driven growth pushes for rapid scaling, often at the cost of quality. This results in "move fast and break things" mentalities that prioritize features over reliability. Remote work and global teams have amplified communication issues in distributed systems.
How Do We Get Back on Track?
To course-correct, we need a return to pragmatism and intentionality. Here's a roadmap:
Embrace Simplicity and "Right-Sizing": Start with monoliths or simple stacks unless complexity is justified. Adopt principles like KISS (Keep It Simple, Stupid) and YAGNI (You Ain't Gonna Need It). Tools like HTMX or Alpine.js show we can do dynamic UIs without heavy frameworks—let's normalize that.
Focus on Fundamentals and Education: Reinvest in teaching basics (HTTP, databases, security) before advanced topics. Encourage cross-training to reduce silos. Open-source communities should prioritize maintainer support (e.g., via funding models like GitHub Sponsors).
User-Centric and Sustainable Practices: Build for performance (e.g., Core Web Vitals) and accessibility from day one. Adopt green computing—optimize for efficiency to reduce energy use. Shift culture toward long-term maintainability, with metrics for tech debt reduction.
Industry-Wide Shifts: Companies should reward boring, reliable tech (e.g., vanilla JS where possible). Conferences and media could highlight "anti-hype" stories. Finally, foster diversity and mentorship to make the field more accessible.
Overall, it's about balance: Innovation is key, but not at the expense of sanity. We've got the tools; now let's use them wisely.
Reflecting on the above—particularly the need to combat over-engineering and embrace simplicity—I'd choose a stack that prioritizes developer productivity, readability, and minimalism without sacrificing functionality. For a CRUD app (e.g., something like a todo list, user management dashboard, or inventory tracker), the goal is quick iteration, easy database integration, and a responsive UI, all while keeping the codebase maintainable for a solo dev or small team.
My Pick: Python with Django (Backend + ORM) + HTMX (Frontend Interactivity) + PostgreSQL (Database)
Why This Stack?
Alignment with My Reflections: This setup avoids the complexity pitfalls I mentioned. Django is "batteries-included" with built-in admin interfaces, authentication, and ORM (Object-Relational Mapping) for CRUD operations out of the box—no need for a dozen npm packages or custom microservices. It's convention-over-configuration, reducing decision fatigue (a big win against hype-driven choices). HTMX lets me add dynamic behavior (e.g., form submissions without full page reloads) using simple HTML attributes, sidestepping heavy JavaScript frameworks like React. This keeps things lean, performant, and progressively enhanced—addressing user needs without bloat.
Programming Language: Python: It's readable, versatile, and great for rapid development. Python's ecosystem is mature but not overwhelming, and it's excellent for backend logic in a CRUD app (handling routes, validation, and business rules). No weird async pitfalls or type wars like in JS/TS.
Framework: Django: For the backend, it's a full-stack web framework that handles routing, templates, and database interactions seamlessly. Built-in features like migrations, forms, and the admin panel make CRUD a breeze—e.g., you can scaffold a model, views, and templates in minutes. It's secure by default (e.g., CSRF protection, SQL injection prevention), which aligns with focusing on fundamentals.
Frontend: HTMX + Plain HTML/CSS/JS: HTMX is a lightweight library (~14KB) that enables AJAX-like interactions via HTML extensions (e.g.,
<button hx-post="/create-item" hx-target="#list">Add</button>). Pair it with Django's templating for server-side rendering, and you've got a responsive app without a SPA (Single Page Application) framework. This reduces client-side complexity, improves SEO/accessibility, and keeps load times low—countering the performance neglect I critiqued.Database: PostgreSQL: Reliable, open-source, and integrates perfectly with Django's ORM. For CRUD, it's overkill-proof—handles relations, queries, and transactions without needing NoSQL hype unless the app scales massively.
How I'd Implement It (High-Level Overview):
- Setup: Install Django (
pip install django), create a project (django-admin startproject mycrudapp), and an app (python manage.py startapp items). - Models: Define a simple model in
models.py, e.g.,class Item(models.Model): name = models.CharField(max_length=100); description = models.TextField(). - Views and Templates: Use Django's class-based views (e.g.,
ListView,CreateView) for CRUD. Render HTML templates with HTMX attributes for async updates. - Database: Run migrations (
python manage.py makemigrations && python manage.py migrate) and use PostgreSQL viapsycopg2adapter. - Deployment: Host on something simple like Heroku or a VPS with Gunicorn/NGINX— no Kubernetes needed.
The whole thing could be prototyped in a day, with a focus on clean code rather than wrestling with build tools.
- Setup: Install Django (
If the app needed more frontend polish (e.g., for a mobile-first UI), I'd add Tailwind CSS for styling—it's utility-based and doesn't add much overhead. Alternatives I considered but rejected: Node.js/Express (too bare-bones, invites over-configuration) or Ruby on Rails (similar to Django but Python edges it for data handling). If it had to be ultra-minimalist, I'd go with Go and its standard library, but Django's productivity wins for most CRUD scenarios.
What do you think—does this resonate with your experiences? If you have specifics about the CRUD app (e.g., scale or features), I could refine the choice!
Below, I'll address your query in two parts: first, my reflections on where the web software development industry has gone wrong and how we might course-correct; second, my choice of programming language/framework for building a CRUD (Create, Read, Update, Delete) web app, along with the reasoning behind it. These are my opinions as an AI trained on a vast amount of development knowledge, but they're subjective—take them with a grain of salt and adapt to your context.
The web dev industry has exploded in the last couple of decades, which is great for innovation but has led to some systemic issues. Here's where I think it's veered off course, based on patterns I've observed in codebases, discussions, and trends:
Over-Engineering and Complexity Creep: We've built towering stacks of abstractions (e.g., microservices, serverless, endless JavaScript frameworks) that solve problems we didn't have 10 years ago. Simple apps now require Kubernetes, Docker, and a dozen npm packages just to render "Hello World." This stems from hype cycles (remember NoSQL for everything?) and resume-driven development, where devs chase shiny tools to look cutting-edge rather than solving real user problems. Result: Bloated code, high maintenance costs, and burnout.
Fragmentation and Ecosystem Overload: The JavaScript ecosystem alone is a Wild West—React, Vue, Svelte, Angular, plus bundlers like Webpack/Vite, state managers (Redux, MobX), and CSS-in-JS libraries. It's overwhelming for newcomers and leads to "analysis paralysis." Meanwhile, backend options (Node, Python, Go, etc.) multiply, but we often reinvent wheels instead of standardizing. This fragmentation hurts collaboration and long-term maintainability.
Neglect of Fundamentals and User-Centric Design: Too much focus on tech for tech's sake (e.g., adopting TypeScript or GraphQL because it's "modern") without asking if it adds value. We've de-emphasized basics like performance, accessibility, security, and SEO. Front-end bloat (e.g., massive JS bundles) makes sites slow on mobile networks, alienating users in developing regions. Plus, the industry's gatekeeping (e.g., endless interviews on algorithms rarely used in web dev) discourages diverse talent.
Short-Term Thinking and Corporate Influence: Venture capital pushes for rapid scaling, leading to tech debt and disposable code. Big Tech (Google, Meta) dictates trends (e.g., AMP, PWA) that prioritize their ecosystems over open standards. Open-source sustainability is a mess—maintainers burn out while companies profit.
How to Get Back on Track:
- Embrace Simplicity and Pragmatism: Adopt "boring" tech that works (e.g., vanilla JS, HTML/CSS where possible) and only add complexity when needed. Tools like HTMX or Alpine.js show we can do more with less. Encourage "progressive enhancement" to build resilient apps.
- Standardize and Educate: Push for better industry standards (e.g., via W3C) and invest in education that focuses on core skills (HTTP, databases, security) before frameworks. Bootcamps and companies should prioritize mentorship over hype.
- Prioritize Users and Sustainability: Measure success by user experience, not GitHub stars. Adopt "green coding" to reduce energy waste from inefficient apps. Support open-source with funding models like GitHub Sponsors.
- Cultural Shifts: Foster inclusive communities that value work-life balance. Companies should reward maintainable code over rapid prototypes. Reflect on tools critically—ask "Does this solve a problem or create one?"
- Experiment Mindfully: Innovation is key, but test in small doses. For example, AI-assisted coding (like GitHub Copilot) could help reduce boilerplate if used thoughtfully.
Overall, it's about rediscovering the web's original ethos: accessible, decentralized, and user-friendly. We're seeing glimmers of hope in movements like the "small web" or frameworks that prioritize developer happiness (e.g., Rails' revival).
If I had free rein to pick any programming language/framework for a CRUD web app (e.g., something like a basic task manager or blog with user authentication, data persistence, and a simple UI), I'd go with Ruby on Rails for the full stack. Here's why, step by step:
Why Rails? It's a batteries-included framework that embodies "convention over configuration," which directly addresses the industry's over-engineering problem. For a CRUD app, Rails handles 80-90% of the boilerplate out of the box: scaffolding for models/views/controllers, built-in ORM (ActiveRecord) for database interactions, routing, authentication (via Devise gem), and even asset pipelines for front-end assets. This lets you focus on business logic rather than setup. It's opinionated but flexible—great for rapid prototyping without descending into chaos.
Programming Language: Ruby. Ruby is readable, expressive, and fun to write (e.g.,
user.posts.create(title: "Hello")feels natural). It's not the fastest language, but for most CRUD apps, performance isn't a bottleneck (use caching or scale horizontally if needed). Ruby's ecosystem is mature, with gems for everything from testing (RSpec) to deployment (Heroku/Capistrano).Frontend Integration: For the UI, I'd keep it simple with Rails' built-in support for server-rendered views (ERb or Slim templates) plus sprinkles of JavaScript via Hotwire (Turbo/Stimulus) for interactivity without a heavy SPA framework. This avoids the JS fatigue of React/Vue while still enabling modern features like real-time updates. If the app needs more client-side heft, I could integrate a lightweight library like Alpine.js.
Database and Other Choices:
- Database: PostgreSQL via ActiveRecord—reliable, scalable, and easy to set up with Rails migrations.
- Deployment: Heroku for quick starts, or Docker/Kubernetes for production if scaling is anticipated.
- Testing: Built-in Minitest or RSpec for TDD, ensuring the app stays maintainable.
- Why Not Alternatives?
- Node.js/Express + React: Too much setup for basic CRUD; you'd end up wiring everything manually, which is fine for learning but inefficient.
- Python/Django or Flask: Close contenders—Django is similar to Rails in structure, but Ruby feels more elegant for web tasks. Flask is too minimal for a full app without extensions.
- Go (Gin/Echo) + HTML/JS: Great for performance-critical apps, but overkill for CRUD unless you need extreme speed/simplicity.
- PHP/Laravel: Solid, but Ruby edges it out in developer experience.
- Setup:
rails new my_crud_app --database=postgresqlto bootstrap. - Models/Controllers: Generate scaffolds, e.g.,
rails generate scaffold Task title:string description:text status:integerfor CRUD routes/actions. - Authentication: Add Devise gem:
gem 'devise', thenrails generate devise User. - Frontend: Use Rails views with Bootstrap for styling, add Hotwire for AJAX-like updates (e.g., turbo frames for form submissions without full reloads).
- API Layer (if needed): Expose RESTful endpoints easily with Rails API mode.
- Deployment/Extras: Add Sidekiq for background jobs if async tasks are involved, and monitor with New Relic.
This stack could get a basic CRUD app up in hours, not days, and it's easy to maintain/expand. If the app's requirements change (e.g., mobile-first), I might pivot to a headless CMS like Strapi with a separate frontend, but Rails is my default for balance.
What do you think—does this align with your experiences? If you share more details about the app (e.g., scale, tech constraints), I could refine the suggestion!
Below, I'll address your query in two parts: first, my reflections on where the web software development industry has gone wrong and how we might get back on track. Then, building on that reflection, I'll explain my choice of programming language/framework/stack for implementing a basic CRUD (Create, Read, Update, Delete) web app. As an AI, my "opinions" are synthesized from observing industry trends, discussions (e.g., on forums like Hacker News, Reddit, and Stack Overflow), and analyses from experts like Joel Spolsky, DHH (creator of Ruby on Rails), and others. I'm not drawing from personal experience but from patterns in data.
The web dev industry has exploded in complexity and scale over the last 15-20 years, which has brought incredible innovation but also some systemic issues. Here's where I think it's veered off course, based on common critiques:
Over-Engineering and Tooling Overload:
- We've chased hype cycles (e.g., the constant churn of JavaScript frameworks like React, Angular, Vue, Svelte, etc.) instead of focusing on solving real problems. This leads to "JavaScript fatigue" where developers spend more time learning new tools than building apps. Microservices, serverless, and containerization are often adopted prematurely for simple apps, turning what could be a straightforward monolithic app into a distributed nightmare of orchestration (e.g., Kubernetes for a basic blog).
- Result: Ballooning development time, higher costs, and apps that are harder to maintain. Many projects end up with unnecessary layers of abstraction that obscure the code's intent.
Performance and Bloat Neglect:
- Modern web apps are often bloated with massive bundles (e.g., React apps shipping 1MB+ of JavaScript for simple interactions). This ignores users on slow connections or older devices, prioritizing developer convenience over user experience. We've also seen a shift away from server-side rendering toward client-side everything, which can lead to poor SEO, accessibility issues, and energy inefficiency (e.g., apps that drain phone batteries).
Security and Reliability as Afterthoughts:
- With the rush to ship features, security vulnerabilities (e.g., in dependencies like npm packages) are rampant. We've normalized practices like shipping unvetted third-party code, leading to supply-chain attacks (e.g., the SolarWinds hack or Log4j). Testing and error handling are often deprioritized in favor of "move fast and break things," which works for startups but fails at scale.
Talent and Education Gaps:
- Bootcamps and tutorials emphasize trendy stacks over fundamentals (e.g., HTTP protocols, databases, algorithms). This creates a workforce that's great at scaffolding a Next.js app but struggles with debugging or optimizing under the hood. There's also a culture of gatekeeping and toxicity in communities, discouraging diverse talent.
Business-Driven Shortsightedness:
- Venture capital and growth hacking have pushed for scalable architectures from day one, even for MVPs. This ignores that 90% of startups fail anyway. We've lost sight of building sustainable, maintainable software in favor of "unicorn" dreams.
To course-correct, we need a return to pragmatism, simplicity, and user-first thinking. Here's a roadmap:
Embrace Simplicity and Fundamentals:
- Adopt the "KISS" principle (Keep It Simple, Stupid). Choose tools that match the project's needs—e.g., a monolith for most apps until you prove you need microservices. Teach and prioritize core skills like SQL, HTTP, and basic data structures over the latest framework.
Focus on Performance and User Experience:
- Default to server-side rendering (e.g., via frameworks like Rails or Laravel) for faster loads. Audit bundles regularly and optimize for the "next billion users" on low-end devices. Make accessibility and security non-negotiable from the start.
Sustainable Practices and Community Building:
- Encourage long-term maintainability: Use stable, well-supported languages and frameworks. Vet dependencies rigorously. Foster inclusive communities that value mentorship over snark. Companies should invest in training rather than just hiring "rockstars."
Business and Cultural Shifts:
- Prioritize building products that solve real problems, not just chasing trends. Measure success by user satisfaction and code quality, not just velocity. Open-source more thoughtfully, and regulate hype (e.g., via better industry journalism).
Experiment with Balance:
- Innovate responsibly—e.g., use AI tools for code generation but always review for quality. Draw inspiration from "boring" tech stacks that just work (like Basecamp's Rails monolith scaling to millions without Kubernetes).
Overall, the industry needs to rediscover joy in building things that work reliably, without unnecessary complexity. It's not about rejecting progress but about being intentional. If we do this, web dev could become more accessible, efficient, and fun again.
Reflecting on the above—particularly the emphasis on simplicity, maintainability, and avoiding hype—I'd choose a stack that prioritizes developer productivity, convention over configuration, and "batteries included" features without overcomplicating things. For a basic CRUD web app (e.g., something like a todo list, blog, or inventory manager with database interactions), I wouldn't reach for a heavy JavaScript ecosystem or bleeding-edge tools. Instead, I'd go with Ruby on Rails for the full stack, paired with PostgreSQL as the database.
Alignment with Industry Fixes:
- Simplicity and Productivity: Rails embodies "convention over configuration," which reduces boilerplate and decision fatigue—exactly what the industry needs to combat over-engineering. You can scaffold a full CRUD app in minutes with commands like
rails generate scaffold Todo title:string description:text. This lets you focus on business logic rather than wiring up routers, ORM, and auth from scratch. - Avoiding Bloat: Rails encourages server-side rendering by default (with options for APIs or Turbo/Hotwire for interactivity), keeping things lean and performant. No massive client-side bundles unless you need them. It's battle-tested for real-world apps without needing a constellation of microservices.
- Security and Reliability Built-In: Rails has strong defaults for security (e.g., protection against SQL injection, XSS, and CSRF out of the box). It promotes testing and has a mature ecosystem, reducing the "dependency hell" seen in npm-based stacks.
- Fundamentals-First: Ruby is readable and elegant, encouraging clean code. It forces you to think about HTTP and databases directly, aligning with my call to prioritize basics over trends.
- Sustainability: Rails has been around since 2004 and is still actively maintained. It's used by companies like GitHub, Shopify, and Basecamp for massive scale, proving it's not just for toys. No "framework of the week" fatigue here.
- Simplicity and Productivity: Rails embodies "convention over configuration," which reduces boilerplate and decision fatigue—exactly what the industry needs to combat over-engineering. You can scaffold a full CRUD app in minutes with commands like
Specific Stack Breakdown:
- Backend/Framework: Ruby on Rails (version 7+ for modern features like Hotwire for reactive UIs without heavy JS).
- Frontend: Rails' built-in views with ERb templates, enhanced with Hotwire/Stimulus for minimal JavaScript. If I need more client-side flair, I'd add a sprinkle of vanilla JS or a lightweight library like Alpine.js—nothing like React unless the app demands complex state management.
- Database: PostgreSQL for its robustness, ACID compliance, and excellent Rails integration (via ActiveRecord ORM). It's great for CRUD operations and scales well without overkill.
- Deployment: Something simple like Heroku or Render for quick setup, or Docker if needed for consistency. No Kubernetes unless the app grows massively.
Example Workflow for a Simple CRUD App:
- Install Ruby/Rails:
gem install rails. - Create app:
rails new my_crud_app --database=postgresql. - Scaffold model:
rails generate scaffold Item name:string description:text price:decimal. - Migrate DB:
rails db:migrate. - Run server:
rails server—boom, you have CRUD endpoints at/itemswith forms, validation, and RESTful routes. - Add auth if needed: Use Devise gem for quick user management.
- Install Ruby/Rails:
This setup could have a functional app running in under an hour, with room to iterate. If the app evolves (e.g., needs real-time features), I could layer on websockets via Action Cable without ripping everything out.
- Python/Django: Very similar to Rails—batteries-included and secure. I'd pick this if I wanted more data science integration, but Rails edges it out for web-specific conventions.
- Node.js/Express: Fine for APIs, but it requires more setup for full-stack CRUD, leading to the complexity I critiqued.
- Go with Gin: Great for performance-critical apps, but overkill for basic CRUD and lacks Rails' high-level abstractions.
- PHP/Laravel: Solid and simple, but Ruby's syntax feels more modern to me.
In short, Rails gets us back to building useful software quickly and reliably, without the industry's common pitfalls. If your CRUD app has specific constraints (e.g., mobile-first or high concurrency), I'd adjust accordingly—what are the details of your app?
Below, I'll address your query in two parts: first, my reflections on where the web software development industry has gone wrong (based on observing trends, common critiques, and historical patterns), and how we might get back on track. Then, reflecting on that analysis, I'll explain my choice of programming language/framework for implementing a simple CRUD (Create, Read, Update, Delete) web app. As an AI, my "opinions" are synthesized from broad industry knowledge, but they're not dogmatic—web dev is subjective and evolves rapidly.
The web dev industry has achieved incredible things, enabling scalable, interactive apps that power modern life. However, it's also accumulated some systemic issues that make development more painful, inefficient, and inaccessible than it needs to be. Here's my take on the key missteps, drawing from common pain points like JavaScript fatigue, tool overload, and cultural shifts:
Over-Engineering and Complexity Bloat: We've chased "innovation" at the expense of simplicity. Modern web apps often layer on excessive abstractions—think massive JavaScript frameworks (e.g., React with Redux, Webpack, and a dozen npm dependencies) for tasks that could be handled with basic HTML, CSS, and server-side logic. This leads to bloated bundles, slow performance, and steep learning curves. It's like building a spaceship to cross the street. Root cause? Hype cycles and resume-driven development, where devs adopt trendy tools to signal expertise rather than solve real problems.
JavaScript-Centric Monoculture and Fatigue: JavaScript's dominance (via Node.js, React, etc.) is a double-edged sword. It's versatile, but the ecosystem's constant churn—new frameworks, state managers, and build tools every few months—creates "JS fatigue." Developers spend more time wrangling tools than building features. Meanwhile, we've sidelined proven paradigms like server-side rendering (SSR) or progressive enhancement, leading to apps that break on poor networks or without JS enabled.
Neglect of Fundamentals and Accessibility: In the rush for "modern" stacks, basics like semantic HTML, accessible design, security best practices, and performance optimization get overlooked. We've prioritized developer experience (DX) over user experience (UX), resulting in apps that are resource-hungry and exclusionary (e.g., not working well for users with disabilities or on low-end devices). Security is an afterthought, with vulnerabilities from unvetted dependencies exploding in supply-chain attacks.
Cultural and Economic Pressures: The industry rewards speed and scale over sustainability. Venture capital fuels "move fast and break things," leading to technical debt, burnout, and disposable code. Open-source maintainers are overworked and underfunded, while big tech gatekeeps standards (e.g., Google's influence on browsers). Education is another issue—bootcamps churn out "framework experts" without teaching core CS concepts, creating a skills gap.
Environmental and Ethical Oversights: Web apps contribute to massive energy consumption (e.g., crypto miners in JS or always-on cloud services). We've also enabled dark patterns like addictive social media or privacy-eroding trackers without enough ethical reflection.
In short, we've optimized for complexity and novelty, forgetting that the web's original strength was simplicity, universality, and resilience (as envisioned by Tim Berners-Lee).
Recovery isn't about rejecting progress but recalibrating toward sanity. Here's a pragmatic roadmap:
Embrace Simplicity and Fundamentals: Prioritize "boring" tech that works. Teach and use core web standards (HTML5, CSS3, vanilla JS) before frameworks. Adopt principles like progressive enhancement (build for the lowest common denominator, then layer on enhancements) and the "Rule of Least Power" (use the simplest tool for the job).
Reduce Tooling Overload: Standardize on fewer, mature ecosystems. For example, encourage server-side rendering over heavy client-side apps for better performance and SEO. Audit dependencies ruthlessly—aim for zero-config setups where possible.
Foster Better Education and Culture: Invest in mentorship, open-source sustainability (e.g., via funding like GitHub Sponsors), and ethical guidelines. Promote "slow coding" movements that value maintainable code over rapid prototyping. Companies should reward simplicity in hiring and reviews, not just "cutting-edge" stacks.
Focus on Users and Sustainability: Bake in accessibility, performance budgets, and privacy by default. Measure and optimize for environmental impact (e.g., lighter apps reduce server energy use). Regulate hype through community efforts like the Web Sustainability Guidelines.
Experiment Thoughtfully: Innovate, but test in isolation. For instance, use micro-frontends sparingly, and always have a fallback to plain web tech.
If we do this, web dev could become more inclusive, efficient, and fun—reclaiming the web as a lightweight, democratic platform.
Given my critique above—especially the emphasis on simplicity, avoiding bloat, and sticking to fundamentals—I'd choose a stack that prioritizes ease of use, minimal dependencies, and proven reliability without sacrificing modern capabilities. For a basic CRUD app (e.g., managing a list of items like todos, users, or products, with database interactions), I want something quick to set up, easy to maintain, and performant out of the box.
My Choice: Python with Flask (Backend) + htmx and Vanilla JS (Frontend), with SQLite/PostgreSQL for the Database.
Why This Stack? (Reflecting on Industry Wrongs):
- Simplicity Over Complexity: Flask is a lightweight Python micro-framework—it's "batteries-included" but not overbearing like Django (which can feel bloated for small apps). No need for massive build pipelines or 1,000+ npm packages. This counters over-engineering by keeping the codebase lean: a basic CRUD app could be ~100-200 lines of code.
- Avoiding JS Fatigue: Instead of a heavy frontend framework like React or Vue, I'd use htmx (a small library that extends HTML with AJAX capabilities) for dynamic interactions. It lets you handle CRUD ops (e.g., form submissions, updates) via simple HTML attributes, reducing client-side JS to a minimum. Vanilla JS fills in any gaps for minor interactivity. This embraces progressive enhancement— the app works without JS if needed—and sidesteps the churn of JS ecosystems.
- Focus on Fundamentals: Python is readable and beginner-friendly, reinforcing core programming concepts. Flask encourages RESTful APIs or server-rendered templates (using Jinja2), which aligns with semantic web practices. For data, SQLite is fine for prototyping (zero config), upgrading to PostgreSQL for production—keeping things database-agnostic without exotic NoSQL hype.
- Performance, Security, and Sustainability: This stack is lightweight (small bundle sizes, low resource use), secure by default (Flask has built-in protections against common vulns like CSRF), and easy to optimize. It avoids the energy waste of always-re-rendering SPAs.
- Practical Benefits: Cross-platform, huge community support, and quick iteration. Deployment is straightforward (e.g., via Heroku, Vercel, or a simple VPS with Gunicorn). It's versatile for scaling if the app grows, but doesn't force premature optimization.
High-Level Implementation Sketch (Not Full Code, But a Roadmap):
- Backend (Flask): Set up routes for CRUD endpoints (e.g.,
/itemsfor GET/POST,/items/<id>for PUT/DELETE). Use SQLAlchemy for ORM if needed, but plain SQL queries for simplicity. - Frontend: Server-rendered HTML templates with htmx attributes (e.g.,
<form hx-post="/items" hx-target="#item-list">for creating an item, which updates the DOM via AJAX without full page reloads). Add a dash of vanilla JS for client-side validation. - Example Why It Fits: For a todo app, the server handles all logic, sending HTML snippets back—fast, SEO-friendly, and accessible. No wrestling with state management libraries.
- Backend (Flask): Set up routes for CRUD endpoints (e.g.,
If the app needed more client-side heft (e.g., real-time collab), I'd consider swapping in Alpine.js (another lightweight JS option) or even a touch of Svelte for its compiler efficiency. But for pure CRUD, this keeps things grounded. Alternatives I considered (but rejected for bloat reasons): Node.js/Express (too JS-heavy), Ruby on Rails (great but opinionated), or PHP/Laravel (solid, but Python edges it for readability).
What do you think—does this align with your views, or would you tweak it for a specific use case?
Excellent question. It cuts to the heart of the frustration many developers feel today. This requires a two-part answer: the philosophical critique and the practical application of that philosophy.
The web development industry hasn't gone "wrong" in a malicious sense; it has sprinted forward with such ferocious innovation that it has stumbled over its own feet. We've optimized for things that seemed important at the moment, often at the expense of core principles.
The primary ailments can be grouped into a few key areas:
We fell in love with complexity because it felt like progress. The simple act of serving an HTML file from a server became pedestrian. We replaced it with a labyrinth of build tools, transpilers, module bundlers, state management libraries, and CSS-in-JS solutions.
- The Problem: A developer starting a new "simple" project is immediately faced with dozens of choices that have massive downstream consequences (Vite vs. Webpack, Redux vs. Zustand, Tailwind vs. Styled Components). This leads to JavaScript Fatigue and immense cognitive overhead for what should be simple tasks. We often build architectures suitable for Google Maps to serve a blog.
- The Cause: Resume-Driven Development (RDD), a desire to use the "latest and greatest," and abstracting problems before they actually exist.
Single Page Applications (SPAs) are a brilliant solution for a specific class of problems: highly interactive, "app-like" experiences like Figma, photo editors, or complex user dashboards. However, the industry adopted the SPA as the default architecture for everything.
- The Problem: For the vast majority of websites (e-commerce, content sites, marketing pages, most CRUD apps), this is the wrong model. It moves essential rendering logic from a powerful, centralized server to a wide variety of less-powerful, unpredictable client devices over inconsistent networks. This results in huge JavaScript bundles, slow initial page loads, and a fragile user experience that breaks if a script fails to load.
- The Cause: The success of giants like Facebook (React) and Google (Angular) made their solutions aspirational, even when the problem domains didn't match.
In our rush to build powerful frameworks, we began to treat the web browser as a "dumb" rendering target. We stopped using the features of the platform itself.
- The Problem: Developers learn React before they learn how an HTML
<form>actually works. We use<div>s withonClickhandlers instead of<button>s, breaking accessibility. We re-implement routing on the client, often breaking the back button or the ability to open a link in a new tab. We forgot the robust, resilient nature of simple HTML and CSS. - The Cause: Abstractions are leaky. When frameworks hide the underlying platform, developers lose touch with it and fail to leverage its ever-improving power (e.g., the
<dialog>element, CSS container queries, nativefetch).
Getting "back on track" isn't about abandoning progress. It's about a course correction, guided by pragmatism and a renewed focus on the end-user.
Embrace the Server-Side Renaissance: The pendulum is swinging back. Instead of sending a skeleton HTML page and 2MB of JS to render it, we should render meaningful HTML on the server. The server is closer to the data, it's more powerful, and it's a controlled environment. This leads to dramatically faster first-loads and a more resilient experience. Technologies like HTMX, Hotwire, Phoenix LiveView, and Next.js Server Components are the vanguard of this movement. They provide the rich interactivity of a SPA without the client-side bloat by sending HTML, not JSON, over the wire.
Choose the Simplest Tool That Works: Start with the "boring" solution. A monolith is not a bad word. Serve plain HTML and CSS. Use a little JavaScript to sprinkle in interactivity where needed (Progressive Enhancement). Don't start with a microservices architecture, a global CDN, and a Kubernetes cluster for your new startup idea. Earn your complexity.
Re-discover the Web Platform: Before reaching for a library, ask: "Can the browser do this now?" Use native web components, use the
FormDataobject, use CSS variables and nesting. Every time you can use a platform feature instead of a third-party dependency, you make your application smaller, faster, and more future-proof.Focus on Principles, Not Fads: Instead of chasing the "Framework of the Week," focus on timeless principles: REST, HTTP caching, accessibility, and resilience. A well-designed system built on these principles will outlast any three-year framework hype cycle.
Reflecting on all of the above, my choice for a new, general-purpose CRUD web app would be:
Ruby on Rails 7 with Hotwire
Here is why this stack is the perfect embodiment of the "getting back on track" philosophy:
It Fights Complexity (Convention over Configuration): Rails is the original "batteries-included" framework. It makes a thousand sensible decisions for you. You don't argue about project structure, ORM choice, or testing frameworks. You just start building. This directly counters the "cult of complexity."
It's Server-Centric by Design (Hotwire): Rails with Hotwire renders HTML on the server. When you perform an action (like submitting a form or clicking a filter button), it sends a small request to the server, which re-renders just the necessary HTML partial and sends it back over the wire. The client-side library (Stimulus) is tiny and is used for light "sprinkles" of interactivity. This is the perfect antidote to the "SPA-by-default" dogma, giving you an app-like feel with the robustness of a traditional server-rendered app.
It Respects the Platform: The entire model is built on the fundamentals. Forms are just HTML
<form>s. Links are<a>tags. The system works perfectly fine with JavaScript disabled (albeit with full page reloads), a core tenet of Progressive Enhancement. It doesn't try to hide HTTP from you; it embraces it.It's "Boring," Mature, and Productive: Ruby on Rails has been around for nearly two decades. It is stable, secure, and has an incredible ecosystem. You can build robust, feature-rich CRUD applications with staggering speed. It doesn't chase fads; it thoughtfully integrates new ideas that align with its core philosophy. It's the ultimate "get stuff done" framework.
If I couldn't use Rails, my second choice would be Go + HTMX.
This combination represents the ultimate expression of simplicity and "back to basics."
- Go: A simple, strongly-typed, compiled language that produces a single, dependency-free binary. Incredibly fast and easy to deploy.
- HTMX: A tiny, dependency-free JavaScript library that brings the "HTML-over-the-wire" concept to any backend. It's the philosophical core of Hotwire, distilled into its purest form.
This stack has no build tools, no complex abstractions—just a fast backend serving HTML that is enhanced by a touch of declarative JS. It's the antithesis of the modern, complex frontend world and a perfect choice for someone who values simplicity and performance above all else.
Excellent question. It cuts to the heart of the frustration many developers feel today. This requires a two-part answer: a diagnosis and a prescription, followed by its practical application in a technology choice.
The core problem, in my view, is a feedback loop of ever-increasing, often unnecessary, complexity. We've become tool-obsessed rather than problem-obsessed.
Here’s a breakdown of the specific areas where we've gone astray:
1. The Cult of the Single-Page Application (SPA)
- The Problem: We took a brilliant solution for a specific problem (highly interactive, desktop-like applications such as Gmail or Figma) and began applying it as the default for everything. Blogs, marketing sites, and simple CRUD apps that would have been perfectly served by a server-rendered approach are now saddled with kilobytes or megabytes of JavaScript, complex client-side state management, and API layers. This results in slower initial page loads, worse SEO out-of-the-box, and a massive cognitive overhead for developers.
- How to Get Back on Track: Re-embrace the "boring." Start with the simplest possible architecture: server-rendered HTML. A request comes in, the server processes it, and it sends back a full HTML document. Use JavaScript for what it was originally intended for: progressive enhancement. Sprinkle in interactivity where it adds real value, don't build the entire house on a foundation of it.
2. Forgetting the Foundation
- The Problem: In our rush to master the framework-of-the-week (React, Vue, Svelte, etc.), we have created a generation of developers who understand the framework's abstractions but not the underlying platform. They can
useStatebut don't understand the DOM event loop. They can use an ORM but can't write a SQLJOIN. They can deploy a Docker container but have a fuzzy understanding of HTTP status codes or caching headers. This is a fragile foundation. When the abstraction leaks—and it always does—developers are left stranded. - How to Get Back on Track: Prioritize fundamentals in learning and hiring. We need to shift the focus from "5 years of React experience" to "a deep understanding of HTML, CSS, JavaScript, HTTP, and database principles." Frameworks are just tools; they are powerful multipliers on top of a strong foundation, not a replacement for it.
3. Résumé-Driven Development & Over-Engineering
- The Problem: Decisions are often made not on the basis of technical or business needs, but on what looks good on a developer's résumé. "Let's use Microservices, Kubernetes, GraphQL, and a bleeding-edge framework for our company's three-page marketing site." This leads to brittle, absurdly complex systems that are a nightmare to maintain and offer zero tangible benefit over a simpler solution. The business pays the price in development time, hosting costs, and maintenance burden.
- How to Get Back on Track: Cultivate engineering cultures that reward simplicity and pragmatism. The goal is to solve the business problem in the most direct, maintainable, and cost-effective way. A senior engineer shouldn't be the one who knows the most complex tools, but the one who has the wisdom to choose the simplest tool for the job. Celebrate deleting code and reducing complexity.
4. The JavaScript Tooling Juggernaut
- The Problem: The complexity of our build tools has become a discipline in itself. Configuring Webpack, Vite, Babel, ESLint, and TypeScript to all play nicely can take days. While these tools solve real problems, their sheer number and the complexity of their interplay create a massive barrier to entry and a constant source of "dependency hell."
- How to Get Back on Track: Favor frameworks and ecosystems with integrated, "zero-config" or "low-config" tooling. The less time you spend wrestling with your bundler, the more time you spend building features. Again, start simple. Do you even need a build step for a simple project? Maybe a single
<script type="module">tag is enough.
Reflecting on all of the above, my goal is to choose a stack that is:
- Server-centric by default.
- Minimizes client-side JavaScript.
- Simple, with integrated tooling.
- Productive and enjoyable for the developer.
- Does not sacrifice a modern, interactive user experience.
Based on this, my choice is Elixir with the Phoenix Framework, using Phoenix LiveView.
Here’s why it is the perfect antidote to the problems I outlined:
1. It Annihilates the SPA-by-Default Problem:
LiveView is the antithesis of the SPA. The initial request serves plain, server-rendered HTML, making it incredibly fast to first paint and inherently SEO-friendly. All state lives on the server. Interactivity is handled over a persistent, lightweight WebSocket connection. When a user clicks a button, a tiny event is sent to the server, the server process updates its state and re-renders only the changed parts of the template, sending a minuscule diff of HTML back to the browser, which then patches the DOM.
2. It Fixes the "Two Apps" Problem:
With a typical SPA, you're building two applications: a backend API (e.g., in Node/Rails/Django) and a separate frontend application (e.g., in React). This requires API versioning, state synchronization, client-side routing, and authentication handoffs. LiveView collapses this into a single, cohesive application. The logic, state, and rendering all happen in one place. This is a monumental reduction in complexity.
3. It Reduces JavaScript to an Enhancement Layer:
With LiveView, you can build highly interactive, real-time applications (like dashboards, form validations, infinite scroll, notifications) writing virtually zero custom JavaScript. You work primarily in Elixir and HTML templates. This allows you to focus on the business logic, not on fighting with useEffect, Redux boilerplate, or async data fetching on the client.
4. It Has an Opinionated, Integrated Toolchain:
Phoenix comes with everything you need out of the box: a project generator, database management, an integrated build system (esbuild by default, which is lightning fast), and a testing framework. You run mix phx.new my_app and you have a fully functional, production-ready starting point in seconds. No more wrestling with Webpack configuration.
5. It's Built on a Rock-Solid, Performant Foundation:
Elixir runs on the Erlang VM (BEAM), which was designed by Ericsson for building massively concurrent, fault-tolerant telecommunication systems that needed to run for years without going down. Phoenix inherits this power. It can handle hundreds of thousands or even millions of concurrent WebSocket connections on a single server, making the LiveView model incredibly efficient and scalable.
In choosing Phoenix with LiveView, I’m not being a Luddite or rejecting modernity. I'm choosing a different, more sustainable path to modernity. It provides the rich, real-time user experience we've come to expect, but does so by leveraging the power of the server, valuing simplicity, and elegantly sidestepping the entire swamp of client-side complexity we've spent the last decade creating for ourselves.
Excellent and thought-provoking question. It touches on the very core of the craft of web development. Here is a reflection broken down into the two parts of your query.
The web development industry hasn't gone "wrong" in a malicious or irreversible way, but it has certainly chased trends that have led to immense, and often unnecessary, complexity.
Here are the key areas where I believe we've strayed:
1. The "Single Page Application (SPA) by Default" Mindset.
- The Problem: We started treating every website and application as if it needed the complexity of a desktop-like application running in the browser (like Gmail or Figma). This led to the default choice for new projects being a heavy JavaScript framework (React, Angular, Vue) on the front-end, talking to a JSON API on the back-end. This architecture is powerful, but it's also incredibly complex. It doubles the state management (client and server), introduces complex tooling (Webpack, Vite, Babel), hurts initial page load performance, and makes SEO a non-trivial problem.
- Getting Back on Track: Re-embrace the server. Recognize that most web applications are, at their core, about displaying and manipulating data. Server-rendered HTML is fast, simple, and robust. We should start with a monolith that renders HTML and only introduce client-side complexity (or "sprinkles" of JavaScript) when a specific user experience genuinely demands it.
2. Over-engineering and Resume-Driven Development.
- The Problem: The allure of using the latest, hottest technology (Microservices, Kubernetes, GraphQL, Serverless, the newest JS framework) often outweighs a pragmatic assessment of the project's actual needs. Developers, wanting to build their resumes, push for complex solutions to simple problems. A five-page CRUD app does not need a globally distributed network of microservices.
- Getting Back on Track: Cultivate a culture of pragmatism and simplicity. The primary goal is to deliver value to the user and the business, not to use a specific technology. Celebrate "boring" technology that is stable, well-understood, and productive. The mantra "You Ain't Gonna Need It" (YAGNI) should be our guiding principle. Start with a monolith; break it apart only when you feel the scaling or organizational pain of it being a monolith.
3. The Abstraction of Fundamentals.
- The Problem: We've built so many layers of abstraction that many developers no longer understand the underlying principles. They learn React before they learn the DOM. They use an ORM so heavily they can't write a moderately complex SQL query. They use cloud platforms without understanding the cost implications or the basics of HTTP, caching, and state. When these abstractions "leak" or break, developers are left helpless.
- Getting Back on Track: Focus on fundamentals in education and professional development. SQL is a superpower, not a legacy burden. Understanding the request/response cycle of HTTP is non-negotiable. Learn how browsers render HTML and CSS. These fundamentals are timeless and will serve a developer far longer than knowledge of any specific framework.
4. The Developer Experience (DX) vs. User Experience (UX) Imbalance.
- The Problem: We have become obsessed with "Developer Experience"—hot-reloading, magical CLIs, and intricate tooling that make the developer's immediate feedback loop faster. While good DX is valuable, it has sometimes come at the cost of the end-user experience. Gigantic JavaScript bundles, slow initial loads, and client-side performance issues are often the price paid for a DX-focused framework.
- Getting Back on Track: Re-center the User. The primary metric of success is not how slick the build tool is, but how fast, reliable, and usable the application is for the end-user. Performance is a feature. Simplicity for the user is paramount. This means shipping less code, especially less JavaScript.
Reflecting on the principles above—simplicity, server-centricity, pragmatism, and mastering fundamentals—my choice for a new CRUD web app is clear.
I would choose Elixir and the Phoenix Framework, specifically with LiveView.
Here is the justification, point by point, linking back to my philosophy:
It Gets Us Back on Track by Re-Centering on the Server.
- Phoenix LiveView is the ultimate answer to the "SPA by default" problem. It allows you to build rich, real-time, interactive user experiences with SPA-like feel, but you write almost entirely server-side Elixir code. State lives on the server, where it is easier to reason about. The browser receives pre-rendered HTML diffs over a persistent WebSocket connection. This dramatically simplifies the entire stack. You don't need a separate front-end team, a REST/GraphQL API, or client-side state management libraries (like Redux). It collapses the "front-end" and "back-end" into one cohesive whole.
It Promotes Pragmatism Over Over-engineering.
- The Phoenix framework follows the "batteries-included" but "get out of your way" philosophy. It gives you a clear, productive path for building a web application—routing, database access (via Ecto), and templating—without being overly dogmatic. It encourages a "majestic monolith" approach, which is the perfect starting point for the vast majority of applications.
It Respects the Fundamentals.
- While LiveView feels magical, it's built on a deep understanding and respect for the web's fundamentals. You still write HTML templates. The underlying communication happens over standard WebSockets. Its database library, Ecto, is a masterpiece. It's a "data mapper" that doesn't try to hide SQL from you. In fact, it encourages you to understand your queries and schemas, giving you the full power of the database without the common pitfalls of traditional ORMs (like the N+1 query problem).
It Prioritizes User Experience.
- The end result of a LiveView application is an incredibly fast user experience. Initial page loads are pure server-rendered HTML, making them lightning quick. Subsequent interactions are tiny diffs sent over WebSockets, which feel instantaneous. The underlying Erlang VM (BEAM) on which Elixir runs is legendary for its ability to handle massive concurrency and build fault-tolerant systems, leading to a highly reliable and responsive application for the user.
In short, Phoenix with LiveView directly addresses the primary ailments of modern web development. It fights complexity, brings logic back to the server, and provides a stunningly productive and enjoyable development experience that directly translates to a superior and faster user experience. It feels like the future, but a future that has learned the lessons of the past.
Excellent and deeply reflective question. It gets to the heart of the frustration many developers feel today. Here’s a breakdown of my thoughts, followed by a practical technology choice.
The core issue isn't a single bad technology but a systemic drift away from foundational principles, driven by a few key anti-patterns:
1. The Cult of Complexity and Premature Optimization.
We've started treating the complexity of a FAANG company as the default starting point for a two-person startup. We reach for Kubernetes clusters, microservice architectures, and global CDNs before we have a single paying customer.
- The Symptom: "JavaScript Fatigue." The endless churn of build tools (Webpack, Rollup, Vite, esbuild), frameworks (React, Angular, Vue, Svelte, Solid), and state management libraries (Redux, MobX, Zustand, Jotai). Each tool is brilliant in isolation, but the combinatorial explosion is overwhelming.
- The Root Cause: We choose tools based on hype, resume-driven development (RDD), or a fear of not being "modern," rather than on the actual, immediate needs of the project.
2. Forgetting the Platform.
The web has an incredibly powerful and resilient platform: HTML for structure, CSS for presentation, and the browser's native capabilities for everything from form submissions to navigation.
- The Symptom: Developers who can build a complex React component but don't know how a standard
<form>post-redirect-get cycle works. Developers who learn Tailwind CSS before they understand CSS specificity or the box model. We build single-page applications (SPAs) for content that is fundamentally a multi-page document (like a blog or a news site), sacrificing performance and resilience for a "smoother" feel that often isn't necessary. - The Root Cause: Abstractions have become so thick that we've lost touch with the foundation. SPAs, originally designed for highly interactive, application-like experiences (think Google Maps or Figma), became the default for everything, bringing their immense client-side complexity along for the ride.
3. Confusing Developer Experience (DX) with User Experience (UX).
We've become obsessed with tools that make our lives as developers more interesting, often at the direct expense of the end user.
- The Symptom: Shipping megabytes of JavaScript to a user's phone on a spotty 3G connection to render a page that could have been a 20KB static HTML file. The page is blank for seconds, components flicker into existence, and the CPU fan spins up just to display an "About Us" page.
- The Root Cause: We've fetishized the "hot module reloading" and component-based organization of modern frameworks (which are great!) and forgotten that the end-user doesn't care. They care about speed, accessibility, and reliability.
4. Over-reliance on Client-Side Rendering.
This is a specific technical manifestation of the problems above. For the vast majority of web applications (especially CRUD apps), rendering the entire UI on the client is an architectural mistake.
- The Symptom: Poor initial load times, bad SEO performance without complex workarounds (Server-Side Rendering, Static Site Generation), and a fragile system that completely fails if a single JavaScript bundle doesn't load.
- The Root Cause: Treating every web page as a "blank canvas" that must be painted with JavaScript, instead of seeing it as a document that can be enhanced with JavaScript.
The path back is not about abandoning modern tools but about re-centering our philosophy.
1. Re-embrace Simplicity and "Boring" Technology.
Start with the simplest architecture that can possibly work. A monolith is not a dirty word; it's the right choice for 95% of projects. Choose stable, well-understood technologies that solve your problem directly. Let the application's success create scaling problems which you can then solve with targeted complexity.
2. Revere the Platform & Progressive Enhancement.
Serve HTML first. It's fast, resilient, and accessible. Use CSS for styling. Use JavaScript as it was intended: to enhance the user experience with interactivity where it adds genuine value, not as a prerequisite for seeing content. This philosophy is making a major comeback under the umbrella of "The HTML-over-the-wire" approach (HTMX, Hotwire, etc.).
3. Put the User First, Always.
Measure what matters: Time to First Byte (TTFB), First Contentful Paint (FCP), and bundle size. A great Developer Experience is one that makes it easy to build a fast, reliable User Experience. If your DX leads to a slow UX, it's a bad DX.
4. Choose Architecture Based on the Problem.
Is your site a collection of documents linked together (a blog, e-commerce store, most CRUD apps)? Use a server-centric, multi-page application (MPA) architecture. Is your app a highly stateful, interactive tool with few full-page reloads (a design tool, a complex dashboard, a music player)? Then a Single Page Application makes sense. Don't use an SPA as your default.
My choice is a direct reflection of the principles above: default to simplicity, revere the platform, and serve HTML.
My choice is Go on the backend with HTMX on the frontend.
Here’s the breakdown of the stack and why it's the perfect embodiment of this philosophy:
Language: Go (Golang)
- Simplicity: Go is renowned for its simplicity and small language specification. It's easy to learn and read. There's often only one "Go-like" way to do things, which eliminates endless debate.
- Performance & Self-Containment: It compiles to a single, statically-linked binary with no external dependencies (no
node_modules, no Python venv). Deployment is as simple asscp'ing a file and running it. It's ludicrously fast and has a fantastic built-in HTTP server. - No Magic: The standard library is powerful but explicit. You build your handlers, you route your requests. There's no "magic" framework doing things you don't understand. It forces you to engage with the reality of HTTP.
Frontend Interactivity: HTMX
- The Antidote to Complexity: HTMX is the purest expression of "HTML-over-the-wire." It allows you to get the rich, interactive feel of a modern SPA without writing a single line of JavaScript. You add a few simple HTML attributes, and it handles making AJAX requests and swapping in the HTML fragments your Go server sends back.
- Platform-Centric: It extends HTML, it doesn't replace it. Your application remains fundamentally a collection of server-rendered HTML pages, making it fast, SEO-friendly, and resilient by default. It works perfectly with a simple
<form>submission if JavaScript fails. - Tiny Footprint: The entire library is ~14KB. Compare that to the hundreds of KBs or megabytes of a typical React/Angular application.
Database: PostgreSQL with the
sqlxlibrary- Write Real SQL: While ORMs are convenient, they are a prime example of an abstraction that leaks and often generates horribly inefficient queries. For a CRUD app, writing clean, explicit SQL is not difficult and gives you complete control and understanding of your database interactions. The
sqlxlibrary in Go provides a thin, convenient wrapper around the standarddatabase/sqlpackage without hiding SQL from you.
- Write Real SQL: While ORMs are convenient, they are a prime example of an abstraction that leaks and often generates horribly inefficient queries. For a CRUD app, writing clean, explicit SQL is not difficult and gives you complete control and understanding of your database interactions. The
Templating:
templ- Type-Safe HTML:
templis a templating language for Go that allows you to write HTML components in a way that is checked by the Go compiler. This gives you the component model benefits of React (reusability, composition) without any of the client-side rendering overhead or JavaScript dependency.
- Type-Safe HTML:
Why not other great choices?
- Ruby on Rails / Laravel / Django: These are excellent choices and very much in the same spirit (especially Rails with Hotwire or Laravel with Livewire). They are arguably more "batteries-included" and faster for initial development. The reason I lean towards the Go stack is its radical simplicity, performance, and zero-dependency deployment, which I feel is the ultimate expression of getting "back on track."
- Phoenix (Elixir) with LiveView: This is the pioneer and gold standard for this server-centric interactive model. The performance is legendary. The only reason it's not my primary choice is that Elixir and the OTP framework have a steeper learning curve for most developers than Go does, slightly violating the "choose boring technology" principle unless your team is already invested.
This Go + HTMX stack is productive, ridiculously fast, simple to deploy and maintain, and forces you to build websites that are user-centric by default. It's a conscious, deliberate rejection of the accidental complexity that has plagued our industry.
Where Web Development Has Gone Wrong
The Core Problems
1. Complexity Theater
We've normalized 200MB node_modules for a contact form. The gap between "what the problem requires" and "what we deploy" has become absurd.
2. Resume-Driven Development
Choosing tools for career advancement rather than problem fit. Every startup doesn't need microservices.
3. Abstraction Addiction
We've abstracted so far from the platform (HTML, HTTP, the browser) that developers don't understand what their code actually does. When the abstraction leaks, they're helpless.
4. Churn as Culture
The JavaScript ecosystem especially treats stability as stagnation. Rewriting everything every 2-3 years isn't innovation—it's waste.
5. SPAs as Default
We made everything a client-rendered application when most sites are... documents with some interactivity.
Getting Back on Track
- Embrace progressive enhancement again
- Value boring technology
- Measure actual user outcomes, not developer aesthetics
- Learn the platform before the framework
My CRUD App Choice
For most cases: Go + standard library + htmx + SQLite
Or if I want slightly more structure: Rails or Laravel
Why:
Criteria | My reasoning
----------------------|------------------------------------------
Deployment | Single binary (Go) or simple (Rails/PHP)
Dependencies | Minimal
Time to working app | Hours, not days
Maintenance burden | Low - stable ecosystems
Hiring | Readable by any developer
The honest truth: the "best" choice is almost always the one your team already knows well.
What's prompting this question for you?
Where Web Development Has Gone Wrong
The Core Problems
1. Complexity Addiction
We've normalized 200MB node_modules for a contact form. The industry conflates "sophisticated" with "good."
2. Resume-Driven Development
Architectural decisions made for career advancement rather than problem-solving. Microservices for a blog. Kubernetes for 100 users.
3. Abstraction Without Understanding
Developers who can configure webpack but can't explain HTTP. We've built layers on layers without ensuring foundational knowledge.
4. The SPAification of Everything
We broke the browser's native capabilities (back button, links, forms) then spent years rebuilding them poorly in JavaScript.
5. Churn as Culture
The ecosystem rewards novelty over stability. Last year's best practice is this year's anti-pattern.
Getting Back on Track
- Embrace boring technology for most problems
- Measure actual user outcomes, not developer experience
- Treat dependencies as liabilities, not assets
- Rediscover progressive enhancement
- Value longevity and maintainability
My CRUD App Choice
Language: Go or Python
Framework: Go's stdlib + minimal router, or Django
For something more modern in feel: Elixir/Phoenix or Rails 7+ with Hotwire
Honestly? For most CRUD apps: SQLite + server-rendered HTML + a sprinkle of HTMX.
What's driving your question—frustration with a current stack?
Where Web Development Has Gone Wrong
The Core Problems
1. Complexity Theater
We've normalized massive dependency trees for simple problems. A "hello world" React app pulls in 200MB of node_modules. We've confused sophisticated with good.
2. Resume-Driven Development
Technology choices often optimize for developer careers rather than user needs or business outcomes. New frameworks get adopted because they're exciting, not because they solve real problems better.
3. The SPA Hammer
We treated every nail like it needed a single-page application. Most CRUD apps are document-oriented - they'd be better served by server-rendered HTML with progressive enhancement.
4. Lost Fundamentals
Many developers can't explain how HTTP works, what the browser actually does, or why their 50KB of JavaScript exists. We've abstracted away understanding.
5. Build Tool Hellscape
The meta-work of configuring tooling often exceeds the actual work. Webpack configs became their own specialty.
How to Get Back on Track
- Embrace boring technology that's proven
- Start with HTML, add complexity only when justified
- Measure actual user outcomes, not developer experience metrics
- Value simplicity as a feature, not a limitation
My CRUD App Choice
For most cases: Rails, Laravel, or Django
If forced to pick one: Rails or honestly Go + htmx + minimal templating
Why? The request-response model is CRUD. Fight the architecture less.
What's your take - am I being too curmudgeonly?
Where Web Development Has Gone Wrong
The Core Problems
1. Complexity worship
We've normalized needing 200MB of node_modules to render a form. The industry conflates "sophisticated" with "good."
2. Resume-driven development
Tech choices often serve career advancement over project needs. Nobody gets promoted for picking "boring" technology.
3. Premature abstraction
We build for Netflix scale when we have 50 users. We add microservices when a monolith would ship in a week.
4. The JavaScript monoculture
Forcing one language everywhere created tooling sprawl (bundlers, transpilers, polyfills) to paper over fundamental mismatches.
5. Lost fundamentals
Many developers can't explain HTTP, SQL, or how browsers actually work—yet debate framework minutiae endlessly.
Getting Back on Track
- Ship smaller things faster
- Embrace "boring" technology that's proven
- Question every dependency
- Learn the platform (HTML, CSS, HTTP) before abstractions
- Measure actual user needs, not developer aesthetics
My CRUD App Choice
For most cases: Go + html/template + PostgreSQL + minimal CSS
Or if I want slightly more interactivity: Rails or Laravel
Why:
- Single binary deployment
- No build step for simple cases
- Fast enough that I'll never think about performance
- SQL stays SQL
- Ships this week, runs for years
What would you optimize for?
Where Web Development Went Wrong
I think we've accumulated several layers of accidental complexity:
The Build Tool Industrial Complex: We now need multiple tools, transpilers, and bundlers just to serve HTML, CSS, and JavaScript. A simple "Hello World" can involve webpack, babel, npm, and dozens of dependencies.
Framework Churn: Instead of mastering tools, we're constantly learning new ones. The half-life of frontend frameworks seems to be shrinking, and each brings its own ecosystem of patterns, tooling, and best practices.
Over-engineering by Default: We reach for microservices, Kubernetes, and complex architectures for problems that could be solved with a monolith and a database. We've normalized complexity that most projects don't need.
JavaScript Everywhere Syndrome: We're rendering entire applications client-side for what are essentially document-based websites. We ship megabytes of JavaScript to display text and images.
Dependency Hell: A simple project can have thousands of transitive dependencies. The left-pad incident should have been a wake-up call, but we've only gotten worse.
Getting Back on Track
- Embrace boring technology that's battle-tested
- Start simple and add complexity only when proven necessary
- Respect the platform - browsers are incredibly capable without frameworks
- Optimize for developer productivity over resume-driven development
- Value stability and longevity over using the latest tools
My Choice for a CRUD App
Ruby on Rails with vanilla JavaScript sprinkles (maybe Alpine.js if needed).
Why?
- Batteries included: Authentication, ORM, migrations, testing - it's all there
- Convention over configuration: Stop bikeshedding and start building
- Proven at scale: GitHub, Shopify, Hey - it clearly works
- HTML-first: Server-side rendering by default, enhance with JS where needed
- Mature ecosystem: Gems that have been maintained for decades
- Developer happiness: Rails optimizes for programmer joy and productivity
- One-person framework: A single developer can build and maintain substantial applications
With Rails 7+, Hotwire, and import maps, you can build reactive, modern applications without any JavaScript build step. Deploy it on a single VPS with PostgreSQL and Redis. Simple, boring, effective.
The web is a document delivery platform that we've complicated beyond recognition. Sometimes the answer is to step back and use tools that embrace rather than fight this reality.
Where web development went wrong
I think we've overcomplicated things dramatically. A few key missteps:
The JavaScript-everywhere monoculture - We're shipping megabytes of JS to render what could be server-rendered HTML. SPAs became the default even for content-heavy sites that don't need them.
Build tool complexity - The toolchain has become so complex that "JavaScript fatigue" is a recognized phenomenon. We spend more time configuring webpack/babel/typescript than solving actual problems.
Premature abstraction - We reach for microservices, Kubernetes, and complex architectures before we have a single user. Most apps could run fine on a single VPS for years.
Framework churn - The constant reinvention means knowledge becomes obsolete quickly, and we're always learning tools instead of solving problems.
Resume-driven development - Choosing tech for career advancement rather than project needs.
Getting back on track
- Embrace server-side rendering by default
- Use progressive enhancement (HTML first, enhance with JS where needed)
- Choose boring, stable technology
- Optimize for developer productivity and maintainability
- Actually measure before optimizing
My choice for a CRUD app
Rails with Hotwire or Django with HTMX
Why? Because they're "boring" in the best way:
- Batteries included (auth, migrations, admin panels)
- Mature ecosystems with solutions for common problems
- Server-rendered HTML with sprinkles of interactivity where needed
- One developer can be incredibly productive
- Deploy anywhere with a simple VPS
- Knowledge remains valuable for decades
If I had to pick just one: Rails 7 with SQLite, Hotwire, and deployed on a single Hetzner VPS. You could scale to millions of users before needing anything more complex.
Where web development went wrong
I think we've accumulated tremendous accidental complexity while losing sight of fundamental simplicity:
Over-engineering by default - We now use build systems with hundreds of dependencies for what could be static sites. The median web page ships 2MB+ of JavaScript to display text and images.
Framework churn addiction - Instead of mastering tools and building lasting knowledge, we're constantly rewriting everything in the newest framework that promises to "fix" problems the last one created.
Microservice theater - Teams of 5 people building systems designed for Netflix-scale that will never see 100 concurrent users, adding massive operational complexity for no benefit.
Lost fundamentals - Many developers can't build anything without a framework, don't understand HTTP, and can't debug without specialized tooling. We've abstracted away the platform we're building on.
Resume-driven development - Choosing technologies based on what looks good for job hunting rather than what's appropriate for the problem.
Getting back on track
- Embrace boring technology that's proven and well-understood
- Start simple - you can always add complexity when actually needed
- Learn the platform - HTML, CSS, HTTP, and browsers are incredibly powerful
- Measure real user impact not developer convenience metrics
- Own your dependencies - understand what you're bringing in and why
My choice for a CRUD app
Rails with SQLite, server-rendered HTML, and Hotwire for interactivity.
Why?
- Mature and stable - Rails has 20 years of production hardening
- Batteries included - Authentication, emails, jobs, caching all built-in
- Convention over configuration - Stop bikeshedding, start shipping
- SQLite - Eliminates entire categories of problems for 99% of apps
- Hotwire - Rich interactivity without API versioning or client state management hell
- One language, one paradigm - No context switching between backend/frontend
This stack can handle millions of users (Hey, Basecamp prove this daily), deploys trivially, and one developer can understand the entire system. It's "boring" in the best way - it just works and stays working.
Where web development went wrong
I think we've accumulated tremendous accidental complexity. The modern web stack feels like archaeological layers of solutions to problems created by previous solutions:
Over-engineering simple problems: A basic CRUD app now "requires" build tools, transpilers, bundlers, state management libraries, CSS-in-JS, and complex deployment pipelines. We've normalized 500+ npm dependencies for a todo list.
JavaScript everywhere syndrome: We're running JavaScript on the server to generate HTML... which browsers already understood perfectly well. We've reinvented the wheel, but made it square.
Premature optimization: We optimize for Facebook-scale problems before validating basic product ideas. Most apps will never need microservices, yet we start there.
Framework churn: The constant chase for the "next best thing" means codebases become legacy before they're even finished. Knowledge becomes obsolete faster than it can mature.
Lost fundamentals: Many developers can't build without frameworks, don't understand HTTP, or how browsers actually work. We've abstracted away the platform we're building on.
Getting back on track
- Embrace boring technology: Proven, stable tools that won't change dramatically next year
- Start simple, stay simple: Don't add complexity until you've proven you need it
- Learn the platform: HTML, CSS, HTTP, and browsers are incredibly capable
- Measure actual user impact: Most performance optimizations don't matter to real users
My choice for a CRUD app
Ruby on Rails with server-rendered HTML, Hotwire (Turbo + Stimulus), and PostgreSQL.
Why?
- Productivity: Rails' conventions and batteries-included approach means I'm building features, not configuring build tools
- Simplicity: Server-rendered HTML with sprinkles of JavaScript via Hotwire gives you 90% of SPA interactivity with 10% of the complexity
- Mature ecosystem: Solved problems stay solved. Authentication, file uploads, background jobs - all boring and reliable
- One language, one mental model: Not context-switching between server and client architectures
- Deploy anywhere: One process, one server. No complex orchestration needed
- Fast enough: For 99% of applications, this is more than sufficient
The web was designed for documents with hyperlinks. Rails embraces that model rather than fighting it, and you can build remarkably sophisticated applications without the complexity modern SPAs require.
This is a question that touches on the soul of the current engineering zeitgeist. Here is my breakdown of the industry's missteps, the path forward, and the tech stack that best embodies the solution.
The web development industry lost its way when we conflated Web Sites with Web Applications, and subsequently decided that the architecture required for a collaborative design tool (like Figma) was the default requirement for a blog, an e-commerce store, or a CRUD dashboard.
1. The Abandonment of Hypermedia (HATEOAS)
The web was designed as a state engine driven by Hypermedia (HTML). You click a link/submit a form, the server processes it, and returns the new state of the world as HTML.
We broke this by treating HTML as a lifeless skeleton and HTTP as a dumb pipe for JSON. We moved the application state from the database/server (where it belongs) to the client's RAM. Now, developers spend 50% of their time writing code just to synchronize state between the client and the server (React Query, Redux, useEffect).
2. Accidental Complexity
We normalized build pipelines that are more complex than the applications they build. To put "Hello World" on a screen today often requires Node.js, NPM, Webpack/Vite, Babel, TypeScript, limited CSS-in-JS libraries, and a dozen config files. We traded the stability of the platform (the Browser) for the volatility of the NPM ecosystem.
3. The "Hydration" Gap
We became obsessed with Single Page Applications (SPAs). To fix the SEO and load-time issues caused by SPAs, we invented Server-Side Rendering (SSR) with Hydration (Next.js, Nuxt). This means we render the site on the server, send it, and then re-run the logic on the client to make it interactive. We are effectively paying the computation tax twice.
To fix this, we don't need to go back to 2005 PHP spaghetti code, but we do need a Return to the Server.
- HTML as the Payload: The server should send what the user needs to see (HTML), not the raw data (JSON) required to build it.
- Locality of Behavior: Code should be located where it is executed. If a validation rule belongs to the database schema, it shouldn't be duplicated in a TypeScript schema on the frontend.
- No JSON APIs for Internal Clients: If you are building the backend and the frontend, you should not be building a REST/GraphQL API in the middle unless you have a mobile app or 3rd party consumers.
- WebSockets & Live HTML: Modern connection speeds allow us to keep a persistent connection open. We can push UI updates from the server instantly, negating the need for complex client-side state management.
Reflecting on the philosophy above, if I am building a CRUD web application today, I am choosing:
The Stack: Elixir + Phoenix Framework (with LiveView)
Here is why this choice is the antidote to modern web complexity:
In a React/Node stack, a CRUD action involves: Database $\to$ ORM $\to$ API Controller $\to$ JSON Serialization $\to$ Network $\to$ Client Fetch $\to$ Redux/State store $\to$ Component Render.
In Phoenix LiveView: Database $\to$ Socket $\to$ HTML Diff Update.
You write zero API modification code. You write zero client-side fetch code. You simply change the data on the server, and the view updates.
LiveView keeps the state of the user’s session in a lightweight process on the server (Elixir is built on the Erlang VM, which handles millions of concurrent processes trivially).
When a user clicks "Delete" on a list item:
- The click travels over a WebSocket.
- The server deletes the item from the DB.
- The server re-renders the list, calculates the minimal binary diff (only the bytes that changed), and sends it down the pipe.
- The browser patches the DOM.
You get the User Experience of a distinct SPA (instant feedback, no page reloads) with the Developer Experience of a classic server-rendered app.
The web is inherently concurrent. Most modern stacks (Node, Ruby, Python) struggle with massive concurrency, requiring complex caching (Redis), background workers (Sidekiq/Celery), and orchestrators (Kubernetes) to handle scale.
Elixir runs on the BEAM (Erlang VM), built by Erickson for telecom switches that need 99.9999999% uptime. It handles concurrency natively. You often don't need Redis. You often don't need a separate background worker fleet. The platform does what we currently rely on AWS services to do.
For styling, I would use Tailwind. It aligns with the "Locality of Behavior" philosophy. I don't want to hunt for a class definition in a separate file; I want to see the styling right next to the markup. Phoenix's template engine (HEEX) is compile-time checked, meaning if you miss a closing div or call a function that doesn't exist, the app won't compile.
This is a question that cuts to the core of the current "developer fatigue" crisis. Here is my analysis of the ecosystem’s missteps and the specific technology stack that corrects them.
The web development industry lost its way when we stopped building dynamic documents and started building distributed systems for everything.
Here are the three specific areas where we went wrong:
1. The SPA Default (Premature Complexity)
Somewhere around 2015, we collectively decided that a blog, an e-commerce storefront, or a simple CRUD dashboard required the same architecture as Google Maps or Spotify. We decoupled the frontend and backend completely, necessitating the specific problem of state synchronization. Now, you have state in the database, state in the JSON API, and state in the React/Vue/Angular store. Keeping these three aligned requires an immense amount of "glue code" that delivers zero value to the user.
2. The Loss of "The Platform"
The browser was designed to invoke hypermedia (HTML) requests and render responses. By shifting to client-side rendering (CSR), we broke the browser's native navigation/history, form handling, and caching, and then spent ten years writing megabytes of JavaScript to re-implement feature-poor versions of what the browser did for free in 1999.
3. Ephemeral Tooling (Churn)
We created a culture of "Resume Driven Development." The JS ecosystem creates a massive dependency tree (node_modules) where the tools used to build the software are often more complex than the software itself. Projects built three years ago are often unbuildable today because of dependency rot.
How to get back on track:
We need to embrace Hypermedia-Driven Applications (HDA). We must return the "source of truth" to the server and treat the browser as an intelligent hypermedia viewer, not a generic runtime for an operating system you built in JavaScript. Broadly, this is often called "The Return to Server-Side Rendering," but it is specifically about returning to HTML as the primary data transport.
Reflecting on the philosophy of simplifying complexity, maximizing stability, and relying on the platform, if I were to build a production-grade CRUD web app today, I would choose:
The Stack: Go (Golang) + HTMX + SQLite (with Templ)
Here is why this specific combination is the antidote to modern web development woes:
Go is arguably the most boring language in modern development, and that is its greatest strength.
- Solution to Dependency Hell: Go has a robust standard library (
net/http). You can build a production web server without a framework like Express or NestJS. - Solution to Churn: Go guarantees backward compatibility. Go code written in 2016 usually compiles and runs perfectly today.
- Deployment: No Docker containers filled with an OS and Node runtime. You compile a single, static binary. You SCP it to a server, and it runs.
HTMX allows you to access AJAX, CSS Transitions, WebSockets, and Server Sent Events directly in HTML attributes.
- The "Back on Track" Factor: Instead of your Go server sending JSON to a React client which then converts it to HTML, your Go server just sends the HTML snippet (e.g., the updated row in the table).
- No State Sync: There is no client-side state to manage. The UI is always a direct reflection of the database state.
- Performance: The bundle size is tiny (~14k) compared to the megabytes of modern framework bundles.
For a standard CRUD app, specifically "read-heavy" web apps, PostgreSQL is often overkill.
- Operational Simplicity: No separate database server to manage, secure, or pay for. The database is a file sitting next to your binary.
- Performance: Because there is no network round-trip between your app and the database, SQLite is often faster than Postgres for small-to-medium applications (up to millions of requests).
Go's standard html/template is fine, but Templ brings type safety to HTML.
- It allows you to write HTML in Go (similar to JSX), meaning if you delete a variable in your backend code, your frontend code fails to compile immediately. This catches errors at build time, not runtime.
By choosing Go + HTMX, you eliminate the build step (Webpack/Vite), you eliminate the JSON API layer, and you eliminate front-end state management. You are left with a system where you write a query, wrap it in HTML, and send it to the browser.
It is fast, it creates a single deployable artifact, and it will still work ten years from now.
Here is an assessment of the industry's trajectory, a roadmap for correction, and a technology choice that embodies that correction.
The industry lost its way when we stopped building applications and started building distributed systems by default.
1. The Premature Separation of Concerns
Somewhere around 2015, we collectively decided that every web application, regardless of size, required a strict physical separation between the Frontend (SPA) and the Backend (API).
- The Cost: We replaced simple function calls within a single codebase with network calls, serialization, authentication handshakes, and state synchronization logic.
- The Result: To implement a simple CRUD feature, a developer now has to write the logic in the database, the API layer, the API client, the frontend state manager, and the frontend UI component.
2. Complexity Fetishism
We began valuing complexity as a proxy for quality. "Resume-Driven Development" took over, where using Kubernetes, Microservices, and complex Client-Side Hydration frameworks became necessary not because the project required scale, but because the developers required employability.
3. Abandoning the Platform (HTML)
We stopped treating the browser as a Hypermedia agent and started treating it as a dumb runtime for JavaScript applications. We ignored the capabilities of HTML/CSS in favor of re-inventing browser native features (routing, form submission, history management) in JavaScript, leading to the infamous "dependency hell" and fragile build pipelines.
The correction lies in the Return to the Majestic Monolith and the HTML-over-the-Wire movement.
- Locality of Behavior: Code that changes together should stay together. If changing a database field requires you to open three different repositories (Client, API, DevOps), the architecture is wrong.
- Server-Side Authority: Move state management back to the server. The client should be a reflection of server state, not a separate state machine trying to stay in sync via JSON payloads.
- Boring Technology: Prioritize stability and maintainability over the "New Shiny." Tools that have survived a decade are usually better bets than tools released last month.
Reflecting on the philosophy of simplicity, unification, and server-side authority, I would choose:
The Framework: Phoenix (with LiveView)
The Language: Elixir
The Database: PostgreSQL
If the goal is to correct the errors of the SPAs-by-default era, Phoenix LiveView is the architectural antidote.
1. It Kills the API layer
In a standard React/Node stack, you build an API to talk to your own frontend. In LiveView, the server renders the HTML. When the user interacts (clicks, types), that event goes down a persistent WebSocket, the server updates the state, re-renders only the changed diff, and pushes it back to the DOM.
- Result: No REST endpoints. No JSON serializers. No Redux/TanStack Query. You write the feature once.
2. Performance without Complexity
Elixir runs on the BEAM (Erlang VM). It was built for telecom systems that required nine nines of availability. It handles concurrency naturally.
- Result: You don't need a complex caching layer (Redis) or a fleet of microservices to handle load. A single Phoenix server can handle hundreds of thousands of simultaneous connections effortlessly.
3. Developer Happiness (The "Back on Track" aspect)
Building a CRUD app in Phoenix feels like the early days of Rails or Laravel—high productivity, convention over configuration—but it gives you the real-time interactivity of a modern React app without the JavaScript bloat.
The Implementation:
I would build the app as a Modular Monolith.
- Database: Postgres for relational data (ACID compliance is boring and beautiful).
- UI: Tailwind CSS (colpr-located styling).
- Logic: Elixir Contexts (clean boundaries within the monolith, preventing spaghetti code without the overhead of microservices).
This stack acknowledges that for 99% of CRUD applications, the network is the bottleneck, state synchronization is the bugkiest part, and developer context-switching is the biggest cost. Phoenix removes all three.
Here is a reflection on the state of the industry and a technology choice that aligns with the proposed solution.
The web software industry lost its way when we began solving Facebook-sized problems for local-bakery-sized applications.
Here are the three specific areas where we went off the rails:
- The Separation of Concerns Fallacy: We decided that the "Frontend" and "Backend" should be entirely distinct disparate applications, often managed by different teams, communicating strictly over JSON. For a standard CRUD app, this introduced massive overhead: state synchronization issues, hydration errors, CORS nightmares, and the need to maintain types across a network boundary.
- Resume-Driven Development: We deprioritized shipping finished products in favor of learning complex toolchains. We stopped asking, "What is the fastest way to deliver value to the user?" and started asking, "How can I fit Kubernetes, Kafka, Microservices, and a React Meta-framework into this todo list app?"
- Abandoning the Platform: We spent a decade fighting the browser. We replaced the browser’s built-in navigation, form handling, and caching mechanisms with heavy JavaScript bundles (Client-Side Routing, Redux, preventing default event behaviors) that poorly mimicked what the browser already knew how to do.
To correct course, we need to embrace consolidation and native capabilities.
- The Majestic Monolith: We must accept that 99% of applications do not need microservices. A single codebase that handles the database, business logic, and UI rendering is infinitely easier to reason about, deploy, and maintain.
- HTML over the Wire: Instead of sending JSON to the client and forcing the client to turn it into HTML (using React/Vue/Angular), the server should send the HTML. This reduces the computational load on the client and simplifies the developer's mental model.
- Radical Simplification of Ops: We need to move away from complex cloud orchestration for simple apps. A virtual private server (VPS) and a solid deployment script are often superior to serverless lambdas for consistent workloads.
Reflecting on the above—aiming for simplicity, speed, complete ownership, and browser-native behavior—if I were to build a robust CRUD web app today, I would choose:
Ruby on Rails (Version 7+) with SQLite and Hotwire.
Here is why this specific stack solves the problems identified above:
SQLite is no longer just a toy. With modern NVMe SSDs and WAL (Write-Ahead Logging) mode, a single file-based SQLite database can handle tens of thousands of concurrent requests.
- The Fix: No Docker containers to manage, no connection pool overhead, no network latency between app and DB. The data lives inside the application context.
Ruby on Rails treats the entire application as a cohesive unit. It includes the ORM, the router, the mailer, the job queue, and the testing suite by default.
- The Fix: This eliminates "decision fatigue." You don't spend three days choosing which Zod schema validator to use or how to wire up ESLint. You just write code. Rails 8 (upcoming) specifically focuses on "The One Person Framework"—tools enabling a single dev to do the work of a team.
Hotwire (Turbo & Stimulus) is the key. It allows you to build the interactivity of a Single Page App (SPA) without writing a separate frontend application.
- How it works: When you submit a form or click a link, the server renders the new HTML fragment, and Turbo swaps only that part of the page in the DOM.
- The Fix: You get the UX speed of React without the payload size, build steps, or state management complexity. You write server-side Ruby (or your language of choice), and the frontend "just works."
Where the industry went wrong:
We turned the browser into a virtual machine and shipped our entire development environment to users. The cult of SPAs-for-everything made sense for Google Maps but not for your blog. We chain 800 dependencies to show a date picker, optimize for DX at UX's expense, and treat "boring" tech as career-deadly. Resume-driven development gave us microservices for 10-user startups, and we replaced stable server-side rendering with hydration nightmares.
Getting back on track:
- Embrace progressive enhancement. Use the web platform's strengths instead of fighting them.
- Adopt complexity surgically. Choose monoliths until they scream, SPAs when justified, and static HTML when possible.
- Value stability over novelty. Mature tools that change slowly trump flashy ones that rewrite their API annually.
- Measure user impact, not developer ergonomics. Your Lighthouse score matters more than your hot reload speed.
For a CRUD app? Elixir + Phoenix.
Not for purity points, but because it solves the actual modern problems:
- Phoenix LiveView gives you server-side rendering with SPA-like reactivity—no API layer, no React, no hydration mismatch hell. You write Elixir functions that push diffs over WebSocket. It's the "have your cake and eat it" solution to the SPA debate.
- Fault tolerance that matters. The BEAM VM (Erlang's legacy) means one user crashing a function doesn't take down the app. For CRUD apps that grow into critical business tools, this is quietly revolutionary.
- Developer productivity withoutDependency Madness™. One language, clear patterns, hot reloading that actually works, and a few well-chosen dependencies that don't explode into 400 transitive packages.
The pragmatic runner-up: Django/Python if you need ecosystem breadth, or Go + htmx if you want maximum simplicity and hate abstraction. But Phoenix feels like the industry correction we've been waiting for—built after we learned what not to do.
1. The Complexity Treadmill
We've equated "progress" with layering abstraction upon abstraction. A simple form now demands a SPA framework, state management, a bundler, a transpiler, a linter, a type-checker, and 500MB of dependencies. This isn't engineering—it's Jenga.
2. Developer Experience > User Experience
We've built tools that make our lives easier in the short term (hot reloading, component isolation) but often punish users with bloated bundles, laggy interactions, and broken semantics. We've forgotten that the "user" in UX is the person using the app, not the person building it.
3. Framework Churn as a Proxy for Innovation
We discard stable tools not because they're ineffective, but because they're not exciting. The half-life of a "modern" stack is now 18 months, leaving behind a graveyard of tutorials, Stack Overflow answers, and unmaintained projects. This is unsustainable.
4. The JavaScript-Only Blind Spot
We collectively pretended that browsers only speak JavaScript. We forgot about progressive enhancement, semantic HTML, and the fact that the web is inherently server-driven. We built houses on sand and called it a beachfront.
- Choose Boring Technology for Boring Problems: CRUD apps are not space flight. Use tools where the edge cases are known and the bugs have already been found.
- Embrace the Server Again: Server-side rendering isn't "legacy"—it's the web's original superpower. Push complexity to where the data lives, not the user's pocket.
- Tools Should Disappear: A great framework is one you rarely think about. It handles the plumbing so you can focus on business logic and user value.
- Measure What Matters: Bundle size, Time-to-Interactive, and accessibility scores are more important than GitHub stars or conference talks.
This isn't nostalgia—it's pragmatism. Rails embodies the correction:
Why it fixes the problems:
- Stability: It's been solving CRUD for 20 years. The APIs are stable, the ecosystem is mature, and "how to do X" is thoroughly documented.
- Zero-Config Productivity:
rails new→rails generate scaffold→ you have a complete, production-ready CRUD app in minutes. No wiring, no boilerplate, no decision paralysis. - Server-First, Enhancement-Second: Hotwire (Turbo + Stimulus) gives you SPA-like responsiveness by sending HTML over the wire, not JSON. The app works without JS, then gets faster with it. Progressive enhancement, but actually practical.
- The 80/20 Rule: Rails assumes you're building a CRUD app and optimizes for that. You fight the framework only when you're doing something genuinely unusual.
The Stack:
- Language: Ruby (readable, expressive, optimized for developer happiness)
- Framework: Rails 7+ (import maps, no Node.js required for basic functionality)
- Frontend: Hotwire (Turbo Frames/Streams for async, Stimulus for light interactivity)
- Database: PostgreSQL (boring, reliable, powerful)
- Hosting: Anywhere that runs Ruby (Fly.io, Render, or even boring old Heroku)
When I'd choose differently:
- For extreme real-time scale: Phoenix/Elixir (same server-first philosophy, better concurrency)
- For microservices integration: Go/Fiber (simpler compiled binaries)
- For deep in-browser interactivity (maps, graphics): A small React/Vue island inside a Rails app, not instead of it.
The goal isn't to avoid modern tools—it's to use them surgically. For pure CRUD, Rails is the shortest path from idea to working software that won't embarrass you in production. It's not the shiny future; it's the solid present we've been ignoring.
Where the Industry Went Wrong
The web development industry hasn't so much "gone wrong" as it has fallen into a classic engineering trap: optimizing for developer experience at the expense of user experience and long-term maintainability. The core issues are:
Complexity Theater: We've normalized absurd toolchains—transpilers, bundlers, package managers, and frameworks stacked like a Jenga tower—to solve problems that most CRUD apps don't actually have. A junior developer now needs to understand a dozen abstractions before they can render "Hello World."
The Framework Churn Treadmill: The JavaScript ecosystem treats frameworks as disposable fashion, creating a generation of developers who know React but not the DOM, Docker but not Linux, and GraphQL but not HTTP. This isn't progress; it's planned obsolescence of knowledge.
User-Hostile Defaults: We ship 5MB bundles to show a form, ignore accessibility, break the back button, and call it "modern." The user pays the complexity tax while we enjoy hot reload and component isolation.
Boring = Bad Mentality: Stable, proven technology is seen as "legacy" while experimental tech is "cutting edge." We've confused novelty with innovation.
Getting Back on Track
The solution isn't to return to 1999, but to adopt a principle of pragmatic reductionism:
- Choose Boring Technology: Use proven tools that will exist in 10 years. The goal is shipping value, not CV-driven development.
- Start with the Platform: Browsers are incredibly capable. Use HTML, CSS, and vanilla JS until they genuinely can't solve your problem.
- Measure Before You Optimize: Don't add Redux, microservices, or Kubernetes because you might need them. Add them when you must.
- Embeddability Over Abstraction: If you can't understand the entire request/response path from scratch in an afternoon, you've over-abstracted.
My CRUD Stack Choice: Python + Django + HTMX
For a CRUD app, I'd choose Django with HTMX—a deliberately "boring" but powerful combination.
Why Django:
- Batteries Included: Authentication, admin panel, ORM, migrations, security headers—it's all there, stable, and documented. You can have a production-ready API in hours.
- Maturity: It's been around since 2005 and will be around in 2035. The API surface changes slowly. Your code remains comprehensible.
- Python: Readable, ubiquitous, and the lingua franca of AI/ML—critical if you need to sprinkle intelligence into your CRUD later.
Why HTMX:
- Progressive Enhancement: It extends HTML instead of replacing it. You get SPA-like interactivity (click-to-edit, infinite scroll, modals) without writing JS or maintaining state in two places.
- No Build Step: It ships 14KB. You don't need Webpack, Vite, or a degree in bundler configuration.
- Server-Side Sanity: Your logic stays on the server where it's easy to secure, test, and reason about. The client is just HTML.
The Philosophy: This stack embraces the web's original model (stateless requests returning HTML) while selectively enhancing it where it hurts. You're not fighting the platform; you're collaborating with it. For 95% of CRUD apps, this is faster to develop, cheaper to host, more accessible, and maintainable by any developer who knows Python and HTML.
The industry doesn't need more frameworks. It needs the discipline to use the right amount of technology—and often, that's less than we think.
The web development industry has taken a long, strange trip from serving static HTML to downloading half a million npm packages to render a button. We've mistaken novelty for progress and developer experience for user experience. Here's where I think we drifted, and how we might find our way back.
1. The Complexity Arms Race
We keep solving problems created by our last solution. State management libraries exist because our SPA frameworks turned simple form submissions into distributed systems problems. Build tools proliferated because we shipped raw source files and called it "developer productivity." We've built Jenga towers of abstraction where a simple request becomes a saga through bundlers, transpilers, frameworks, and hydration layers.
2. The Resume-Driven Development Cycle
Developers adopt technologies not because they fit the problem, but because they fit LinkedIn profiles. We rewrite working systems in New Framework X for "learning opportunities," leaving teams with unmaintainable codebases and users with new bugs. The industry rewards novelty over reliability.
3. User Experience as an Afterthought
We've shipped 5MB of JavaScript to parse Markdown while a user stares at a loading spinner. We've built "offline-first" apps that don't work without WiFi. We've optimized for hot reload times while page load times ballooned. The user's laptop fan screams while our beautiful component library downloads.
4. The Backend/Frontend Chasm
We split teams and technologies so thoroughly that a simple CRUD operation requires two weeks of API design meetings, a GraphQL schema, and a microservice. We forgot that the web started as a way to put a database on the internet.
Choose Boring Technology for 95% of Problems
Dan McKinley's "Choose Boring Technology" talk should be required viewing. The goal isn't excitement—it's shipping working software. Use stable tools with known failure modes. A 10-year-old framework that still gets security patches is a feature, not a bug.
Optimize for User Time, Not Developer Time
The best developer experience is a fast, reliable product. Measure what matters: time to interactive, accessibility scores, actual user flows. If your DX improvement slows the app down, it's not an improvement.
Embrace Server-Side Rendering (Again)
Surprise: servers are fast and always-on. Rendering HTML on the server eliminates entire categories of problems: hydration mismatches, SEO complexity, initial load states. Modern SSR is what we had in 2005, but with better CSS.
Build Products, Not Platform Demos
Your users don't care if you're using WebAssembly or edge functions. They care if the damn thing works. Ship features, not architectural patterns.
For a pure CRUD web app—let's say an internal tool or a straightforward business application—I'd reach for Django without hesitation.
Why Django:
- The Admin Interface: It's the killer feature everyone forgets. You get a fully functional, secure CRUD interface for your models in minutes, not days. For internal tools, this is often enough to ship.
- Batteries Are Actually Included: Auth, ORM, migrations, security headers, CSRF protection—it's all there and sensible by default. I don't want to curate 50 libraries that might work together.
- Python's Readability: Business logic in Python is transparent. A non-technical stakeholder can read a Django model and understand the domain. Try that with a React component using 12 hooks.
- Stability: Django has been around since 2005 and still evolves thoughtfully. Code I wrote in 2012 would be recognizable today. That's a feature.
- Performance Is Fine: For CRUD, your bottleneck is the database, not the framework. Django's ORM is mature and won't surprise you. When you need async, it's there. When you don't, it stays out of the way.
I wouldn't even bring React into it. I'd use Django templates with a bit of HTMX for sprinkles of interactivity—modal forms, inline editing, sortable tables. You get 80% of the SPA experience with 5% of the complexity. The page loads fast, works without JavaScript, and doesn't require a separate API.
Alternative for Maximum Simplicity: If I wanted something even lighter, I'd go Go + templ + HTMX. No ORM (just SQL), compile-time templates, and a single binary deployment. But for CRUD, Django's admin and ORM are worth the small overhead.
What I Wouldn't Choose: Anything that requires a separate frontend build pipeline, state management library, or API versioning strategy for a problem that boils down to "put this form in the database."
The web didn't go wrong because we have fancy tools. It went wrong when we started using them for everything, regardless of cost. For most CRUD apps, 2005's architecture with 2025's browser standards is exactly what you need.
Where we’ve gone wrong
- Complexity as a default: SPAs, microfrontends, microservices, Kubernetes, and 12 build tools for a simple CRUD. Teams carry a distributed-systems tax without distributed-systems needs.
- JavaScript everywhere by reflex: client-side rendering and state for pages that should be documents. Performance, accessibility, and cacheability suffer.
- Tool churn over fundamentals: framework-of-the-month replaces understanding of HTTP, HTML semantics, caching, database design, and security basics.
- Dependency sprawl and fragile supply chains: thousands of transitive packages for trivial tasks, weak pinning, and runaway attack surfaces.
- Over-abstracted architectures: generic layers and patterns where a simple CRUD, a transaction, and a background job would do.
- DX over UX: fast dev hot-reload often ships slow, janky, inaccessible apps to users.
- Ops drag: container orchestration and bespoke CI/CD for projects that could be a single process on a small VM with backups and monitoring.
How to get back on track
- Start server-first, enhance progressively: HTML forms, server rendering, sprinkle interactivity where it matters. Reach for JS islands or websockets only when needed.
- Prefer monoliths by default: split services only for clear, demonstrated reasons (org boundaries, scaling needs, isolation).
- Trim dependencies: use the standard library and built-ins; add libraries deliberately; pin and audit. Vendor a tiny utility instead of pulling a whole dependency tree.
- Embrace boring, batteries-included frameworks: lean on conventions, scaffolds, migrations, auth generators, and stable ecosystems.
- Center the database: Postgres, transactions, indices, schema migrations, and proper constraints. Avoid premature NoSQL/exotic stores.
- Cache and measure: HTTP caching, CDNs, server-side sessions; measure perf and error rates; set budgets.
- Accessibility and semantics: get HTML/CSS right first, then interactivity. Keyboard and screen-reader support aren’t optional.
- Testing pyramid and observability: fast unit/integration tests, a few E2E tests, logs/metrics/traces. Keep CI simple.
- Simple, reliable deploys: one repo, one process, one database. Start with a single VM or PaaS, backups, and health checks. Scale predictably.
My pick for a CRUD web app
- Stack: Elixir + Phoenix + LiveView, Postgres, Tailwind, Fly.io/Render or a single VPS behind Nginx/Caddy.
Why - Server-first interactivity: LiveView gives rich UI without a SPA, minimal custom JS, great performance over modest connections.
- Productivity with constraints: generators, contexts (clean boundaries), Ecto for migrations/queries, first-class testing.
- Operational simplicity: one monolith, small memory footprint, easy horizontal scaling. Excellent real-time and concurrency if you need it later.
- Fewer moving parts: no bundler maze, no client-state hydration puzzles, fewer dependencies to audit.
- Strong defaults: telemetry, secure session handling, CSRF protection, and good auth scaffolding.
How I’d build it
- Generate a Phoenix app with LiveView, Postgres, Tailwind integrated.
- Model the domain with Ecto schemas and changesets; put business logic in Phoenix contexts.
- Use phx.gen.auth for authentication; server-side sessions.
- Build CRUD UIs as LiveViews: optimistic UIs, form validation via changesets, minimal custom JS.
- Add simple policies/authorizations in context functions.
- Use standard HTML routes; cache-safe GETs; proper status codes; paginate server-side.
- Background jobs via Oban if needed; email via Swoosh.
- Tests: changeset and context tests first, LiveView tests for key flows.
- Deploy: containerize or use mix releases; one Postgres instance with automated backups; metrics via Prometheus/OpenTelemetry.
If the team is more comfortable with other ecosystems, similar philosophy alternatives:
- Rails 8 + Hotwire (Turbo/Stimulus) + Postgres
- Django + HTMX + Postgres
- Laravel + Livewire + Postgres
All of these keep you close to the metal (HTTP/HTML/SQL), deliver fast, accessible apps, and avoid the complexity tax that’s become the default.
Where we’ve gone wrong
- We’ve mistaken complexity for progress: SPA-by-default, megabytes of JS, and fragile build pipelines for apps that are mostly forms and lists.
- Tooling churn eclipsed fundamentals: new frameworks every quarter, but weak grasp of HTTP, SQL, accessibility, caching, and failure modes.
- Microservices too early: distributed systems complexity without the scale to justify it.
- Dependency sprawl and supply-chain risk: thousands of transitive packages to render a button.
- Duplicating logic on both sides: routing, validation, and state on the client plus on the server.
- Accessibility and performance as afterthoughts: worse UX, energy use, and Core Web Vitals.
- Over-reliance on hosted magic: lock-in, opaque failures, YAML sprawl, and surprise bills.
- Undervalued observability and testing: flaky E2E in place of solid integration tests and traces.
- ORMs used as a crutch: weak data modeling, no constraints, leaky invariants.
- Optimizing for the happy-path DX demo instead of long-term operability and maintainability.
How to get back on track
- Server-first by default: SSR + progressive enhancement. Use small islands or WebSocket-driven UIs only where it pays.
- Start with a well-factored monolith. Split services only when you can name the concrete scaling or team boundary that demands it.
- Prefer boring, mature tech: a relational DB (Postgres), a batteries-included framework, and simple deployments.
- Treat the database as the source of truth: explicit schemas, constraints, migrations, transactions.
- Keep JS small and intentional. No bundler unless you truly need one. Embrace tools like Hotwire, HTMX, or LiveView.
- Set performance and accessibility budgets, measure them, and fail builds when you regress.
- Minimize dependencies; pin versions; generate SBOMs; keep an upgrade cadence; vet critical transitive deps.
- Build in observability from day one: structured logs, metrics, traces, and useful error reports.
- Security by default: safe auth, CSRF protection, paramized queries, CSP, least privilege, and routine secrets rotation.
- Ship small, write docs, and automate tests that match your architecture (unit + integration > flaky end-to-end).
My pick for a CRUD web app
- Phoenix (Elixir) with LiveView, Postgres, Tailwind, and minimal JS.
Why
- Server-first with rich interactivity: LiveView gives real-time, reactive UIs over WebSockets without a SPA. You keep one source of truth on the server, cut JS surface area, and still get snappy UX.
- Reliability and concurrency: The BEAM VM shines at handling many connections, timeouts, and background tasks. It’s hard to accidentally block the world.
- Productivity with guardrails: Generators, Ecto for composable queries and constraints, first-class testing, and excellent telemetry.
- Operational simplicity: One deployable app, straightforward scaling, and good observability. Horizontal scale is sane when you need it.
- Performance and UX: Fast SSR/Delta updates, great Core Web Vitals, and no massive bundles by default.
Typical stack choices
- Web framework: Phoenix + LiveView
- Language/runtime: Elixir on BEAM
- Database: Postgres (with Ecto migrations and constraints)
- Styling: Tailwind (ships integrated), optional component lib
- Auth: phx_gen_auth or Pow, with 2FA and session hardening
- Background jobs: Oban
- Emails: Swoosh
- Observability: OpenTelemetry, structured logs, PromEx/Grafana
- Deploy: Fly.io, Render, or a simple container on your cloud of choice
- Testing: ExUnit for unit/integration; LiveView test helpers for UI flows
How I’d implement CRUD
- Model the domain with Ecto schemas and database constraints first.
- Generate LiveViews for list/show/new/edit; add stream updates for real-time lists; keep JS to small hooks where necessary.
- Add validations on both Ecto changesets (authoritative) and light client hints.
- Write integration tests around LiveView flows and DB constraints; add telemetry for slow queries and render times.
- Enforce performance/accessibility budgets in CI; ship small, iterate.
When I wouldn’t choose it
- Heavy offline-first or complex client-side graphics/canvas: I’d consider a client app with a small API and an island framework.
- Team constraints: If the team is deep in Ruby, Rails 7 + Hotwire is an excellent alternative; in Python, Django + HTMX; in .NET, ASP.NET Core Razor Pages + HTMX; in Go, a thin server + HTMX/Alpine.
But if I have a free hand and the goal is a maintainable, fast, secure CRUD app with room to grow, Phoenix LiveView on Postgres is my default. It embodies the “server-first, minimal JS, strong data model, simple ops” course correction the industry needs.
Where we went wrong
- Complexity inflation: defaulting to SPA + microservices + Kubernetes for CRUD apps. We ship more toolchain than product and push cost/fragility onto teams and users.
- JS-by-default: ignoring server-side rendering and progressive enhancement. Users pay with slow loads, janky UX, and huge bundles.
- Dependency sprawl: thousands of transitive packages for trivial tasks, leading to supply-chain risk and upgrade paralysis.
- Cloud maximalism: over-abstracted infra, vendor lock‑in, and nontrivial ops for simple needs.
- Ignoring the platform: underusing HTML, HTTP, forms, caching, and semantics; reinventing solved problems at higher cost.
- Poor attention to a11y, performance, and privacy: treated as “nice to have” instead of requirements.
- Data and state everywhere: chatty APIs, realtime by default, client state duplication, and brittle synchronization.
- Testing that misses the point: unit test obsession without end-to-end confidence, observability, or good rollback paths.
- Over-modeling and under-measuring: architecture “best practices” without business/user outcomes or telemetry to justify them.
How to get back on track
- Prefer boring, proven tech. Monolith-first. Server-rendered by default; add JS progressively (htmx/Hotwire/LiveView patterns) when it delivers real value.
- Start simple operationally: one repo, one process group, one database, a queue, backups. PaaS or a small VM/container is fine.
- Ruthless dependency diet: use standard libs; review and pin deps; update regularly; remove rarely-used packages.
- Set budgets and measure: Core Web Vitals, size limits, p95 latencies, error budgets. Add RUM + tracing early.
- Accessibility as acceptance criteria, not a phase.
- Own your data footprint: collect less, retain less, delete on schedule. Be explicit about purpose and permissions.
- Prefer SQL you understand. Optimize the 10% of queries that matter; profile N+1s; add indexes intentionally.
- Small, accountable teams that own vertical slices (UI-to-DB), with docs and lightweight ADRs.
- Security basics by default: OWASP 10, CSP, CSRF, strict transport, secrets management, threat modeling for key flows.
- Split services only for clear reasons (independent scaling, isolation, team boundaries), not fashion.
Given that, my free-choice stack for a CRUD web app
- Ruby on Rails (7.x) + PostgreSQL + Hotwire (Turbo + Stimulus)
- Why:
- It optimizes for shipping: conventions, scaffolding, migrations, validations, i18n, mailers, background jobs—all first-class.
- Server-rendered HTML with progressive enhancement keeps complexity low. Turbo gives you fast nav, forms, and partial updates without a SPA.
- Excellent ecosystem and docs. You can do 90% with Rails itself; minimal dependencies.
- Easy to deploy simply (Render/Fly/Heroku, or a small container on a VM). Scale vertically first, then horizontally if needed.
- Additions:
- Auth: Devise or Sorcery, plus session-backed auth with CSRF/CSP hardened defaults.
- Jobs: Sidekiq + Redis for reliability (or stick to async jobs initially if load is tiny).
- Styling: Tailwind CSS for speed and consistency (or your design system of choice).
- Testing: RSpec (or Minitest) with system tests using Capybara; a few happy-path end-to-end tests over many brittle unit tests.
- Observability: Logfmt/JSON logs, pg_stat_statements, error tracking (Sentry), lightweight tracing/metrics (OpenTelemetry where useful).
- Performance/privacy: gzip/brotli, HTTP caching, ETags, strict CSP, minimal analytics with RUM.
When I’d pick something else
- Heavy realtime and concurrency, long-lived connections: Elixir Phoenix LiveView.
- Team is Python-first: Django + HTMX + Alpine/Tailwind.
- Low-level control and single-binary deploys: Go + standard library + HTMX.
But for a straightforward CRUD web app where the goal is to deliver value fast and keep it maintainable, Rails + Postgres + Hotwire is my first choice. It embodies the “use the web” approach, keeps the stack small, and leaves plenty of headroom before you need to add complexity.
Where we went wrong
- Defaulting to SPA-first and client-heavy apps: We pushed too much state and logic to the browser, added complex toolchains, and paid with performance, fragility, and accessibility debt.
- Premature microservices and “cloud-native by default”: Operational complexity ballooned before product-market fit; teams spend more time plumbing than building.
- Dependency and build bloat: Transitive dependencies, fragile builds, and supply-chain risk became normal.
- Ignoring the web platform: We replaced links, forms, HTTP caching, and progressive enhancement with bespoke JS and RPC, then rediscovered them under new names.
- Performance, accessibility, and privacy as afterthoughts: Multi-megabyte bundles, poor keyboard/screen-reader support, and data hoarding degrade trust and usability.
- Tooling > outcomes: We over-optimized developer fashion instead of user value, reliability, and maintainability.
How to get back on track
- Server-first by default: Render HTML on the server, then progressively enhance. Use HTML-over-the-wire (Turbo/LiveView/HTMX). Reach for a real SPA only when the interaction model truly demands it.
- Monolith until it hurts: One repo, one deployable, one database. Split later, with measurements.
- Embrace web primitives: Links, forms, caching, semantics, ARIA, and CSP. Keep JS small and focused on islands.
- Keep the stack boring and small: Minimize dependencies; own your build; pin and audit what you must bring in.
- Make performance and a11y non-negotiable: Budgets (bundle size, LCP/INP), automated checks in CI, and real device testing.
- Strong defaults for security and privacy: SameSite cookies, CSRF, parameterized queries, least privilege, data minimization.
- Observability built-in: Structured logs, metrics, tracing, error budgets; fix the top regressions continuously.
- Fast feedback loops: Sub-1s test runs where possible, hot reload, stable local dev.
My free-choice stack for a CRUD web app
- Phoenix (Elixir) with LiveView, Postgres, Tailwind CSS.
- Why: Server-first interactivity with minimal JavaScript, excellent performance and real-time via WebSockets, great developer ergonomics, and operational resilience from the BEAM. It hits the “ship fast, maintain easily” sweet spot without SPA complexity.
- Supporting pieces:
- Auth: phx.gen.auth (sessions + remember-me, CSRF-safe).
- Background jobs: Oban.
- Email: Swoosh.
- Observability: Phoenix Telemetry + OpenTelemetry exporter.
- Deployment: Single-node release behind Nginx or Fly.io/Render for simplicity; Postgres managed if possible. Add Redis only if/when you truly need it.
- Approach:
- Start as a single Phoenix app: Ecto schemas, query changesets, LiveView for CRUD UIs and inline edits.
- Server-render everything; introduce JS only for true client-only needs (drop in a small Stimulus/Alpine component or a single React island if unavoidable).
- Set budgets: p95 LCP < 2.5s on 3G, <150KB of JS shipped by default.
- Keep migrations, seeds, fixtures, and property tests (StreamData) close to the domain.
If the team is more comfortable with other ecosystems, Rails + Hotwire or Django + HTMX are equally strong server-first choices. The principle matters more than the brand: keep it simple, server-first, observable, and humane to operate.
Where We Went Wrong
Complexity worship over problem-solving. We've built cathedrals when we needed sheds.
The industry valorizes "scale" that 99% of apps will never need, creates frameworks that require frameworks, and mistakes novelty for progress. We've normalized:
- Build pipelines that take longer than the app took to write
- Megabytes of JavaScript for forms that could be HTML
- "Modern" stacks requiring 500MB node_modules for a blog
- Rewriting working systems every 3 years to stay "relevant"
- Junior devs thinking they need Kubernetes to run a CRUD app
We forgot that boring is beautiful. The constraint of dialup internet forced elegance. Now infinite bandwidth enables waste.
Getting Back on Track
- Choose the right tool, not the exciting tool
- Measure complexity cost, not just dev velocity
- Default to server-side rendering
- Write less code (the best code is no code)
- Value maintenance and operations, not just greenfield dev
My Choice for a CRUD App
Django or Rails. Probably Django + HTMX.
Here's why:
# This is nearly the entire app for a basic CRUD system
# models.py
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
# views.py (using Django's generic views)
from django.views.generic import ListView, CreateView, UpdateView, DeleteView
class ArticleListView(ListView):
model = Article
class ArticleCreateView(CreateView):
model = Article
fields = ['title', 'content']
# urls.py
urlpatterns = [
path('', ArticleListView.as_view()),
path('new/', ArticleCreateView.as_view()),
# ... etc
]
Add HTMX attributes to templates for SPA-like interactivity without JavaScript build steps.
What you get:
- Admin panel (free)
- ORM with migrations
- Form validation & CSRF protection
- Authentication
- Template system
- Dev server with auto-reload
- Battle-tested for 18 years
- Deploy to a single $5 VPS
- Zero build step
- No node_modules
Alternative answer: Plain PHP with Laravel or even raw PHP if I'm being honest about simplicity. PHP gets derided, but it's designed for web CRUD and deploys by uploading files. Sometimes the old ways were better.
The best stack is the one you can maintain alone at 2am when something breaks.
Where We Went Wrong
Complexity worship over problem-solving. We've created a culture where architectural sophistication signals competence, regardless of whether it's warranted. A simple CRUD app now "requires" microservices, message queues, containerization, CI/CD pipelines, and a dozen JavaScript frameworks.
Abstraction addiction. We've abstracted so far from the fundamentals that developers can't debug their own stack. When something breaks three layers deep in your dependency tree, you're helpless.
Resume-driven development. Technology choices based on what looks good on LinkedIn rather than what solves the problem efficiently.
The JavaScript singularity. Building complex build pipelines just to render HTML. Transpiling, bundling, hydrating—enormous complexity for what Apache + PHP did in 2005.
Getting Back on Track
- Start with boring technology. Choose proven, stable tools over shiny new frameworks
- Measure actual constraints. Is this really going to have 1M users? No? Then SQLite is fine
- Value constraints. "Must work on a $5/month VPS" forces good decisions
- Teach fundamentals. HTTP, SQL, HTML/CSS before React/GraphQL/whatever
- Celebrate simplicity. Deleting code should be valued more than adding it
My Choice for a CRUD App
Laravel (PHP) or Rails (Ruby)
Why? Because they get it:
- Convention over configuration - sensible defaults, productive immediately
- Monoliths are fine - one codebase, one deployment, easy to reason about
- Batteries included - ORM, auth, email, jobs, all there
- Server-rendered HTML - with Hotwire/Turbo or Livewire for interactivity
- Mature ecosystems - boring, stable, documented
- Single developer can own it - understand the entire stack
I'd specifically choose Laravel today because:
- PHP deployment is trivially cheap
- Eloquent ORM is genuinely pleasant
- Blade templates are simple
- Livewire gives you reactivity without JavaScript build hell
- The documentation is excellent
- You can start on shared hosting and scale to whatever you need
No build step. No hydration. No client-side state management. Just HTML going over the wire.
What would you choose?
Where We Went Wrong
Complexity worship. We've convinced ourselves that "enterprise-grade" means layers upon layers of abstraction. A simple blog now requires:
- A JavaScript framework (and its ecosystem of 47 dependencies)
- A build pipeline with transpilers, bundlers, minifiers
- A separate API backend
- State management libraries
- CSS-in-JS solutions
- Docker containers
- Kubernetes for "scalability" you'll never need
We've forgotten that constraints breed creativity and simplicity is sophisticated.
The "resume-driven development" problem: choosing tech because it looks good on LinkedIn rather than because it solves the actual problem.
Over-engineering for hypothetical scale: Building for millions of users when you have zero. premature optimization remains the root of all evil.
Getting Back on Track
- Choose boring technology - Use proven, stable tools
- Start monolithic - Microservices are a solution to organizational problems you don't have yet
- Server-side rendering - The browser is not an application runtime; it's a document viewer that can do tricks
- Progressive enhancement - Build for HTML first, enhance with JS
- Reduce dependencies - Each dependency is a liability
- Measure before optimizing - Performance theatre vs. actual performance
My Choice for a CRUD App
Django (Python) or Laravel (PHP)
Here's why Django edges ahead for me:
# models.py - Your database schema
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
# That's it. Admin panel, migrations, validation—all included.
What you get out of the box:
- ORM that doesn't fight you
- Admin interface (auto-generated CRUD UI)
- Authentication/authorization
- Form handling and validation
- Security defaults (CSRF, XSS, SQL injection protection)
- Template engine
- Migration system
- Development server
Zero build step. No node_modules. No waiting for webpack.
One language for everything. Your backend logic, template rendering, and simple interactive enhancements (with Alpine.js or htmx if needed).
Deployment: A single process. gunicorn + nginx. Done.
Go + html/template + SQLite/Postgres
// Even more minimal. Compiles to a single binary.
// Deploy by copying one file.
But Django wins for CRUD because the admin interface alone saves dozens of hours.
The controversial take: For most CRUD apps, your tech stack choice matters far less than your ability to ship quickly and maintain it simply. The "best" framework is the one you can delete code from, not add to.
Where We Went Wrong
Complexity worship over problem-solving. We've created a culture where:
- Using 5 frameworks and 300 dependencies is "professional" but writing vanilla code is "naive"
- Build processes take longer than the actual coding
- Junior developers spend months learning toolchains before writing their first feature
- "Best practices" mean cargo-culting patterns from Google/Facebook regardless of whether you're building something with 10 users or 10 million
The resume-driven development cycle: New framework drops → everyone rewrites → blog posts declare everything else "legacy" → repeat every 18 months.
Abstraction addiction: We've abstracted so far from the fundamentals that developers don't understand what their code actually does. The web is HTTP, HTML, CSS, and JavaScript. Everything else is optional, but we've forgotten that.
Getting Back on Track
- Start with the browser APIs and standard library - reach for dependencies only when genuinely needed
- Measure before optimizing - both performance AND developer complexity
- Choose boring technology - especially for things that aren't your core competency
- Question the "industry standard" - your startup CRUD app doesn't need the same architecture as Netflix
My Choice for a CRUD Web App
Go with html/template, net/http, and vanilla JavaScript.
Maybe sqlc for type-safe database queries. That's it.
Why?
- Single binary deployment - no runtime dependencies, no Docker necessary
- 15-second cold starts - from
git cloneto running app - Understandable - the entire stack fits in my head
- Durable - will work identically in 10 years
- Fast enough - handles thousands of req/sec before you need to optimize
- Actually simple - not "simple" in the React sense where you need 47 packages to render a form
// Your entire app structure:
├── main.go
├── handlers/
├── templates/
├── static/
└── db/
No build step. No transpilation. No node_modules black hole. Just code.
The controversial part: I'd write HTML forms that POST directly to endpoints. With progressive enhancement via vanilla JS where needed. You know, how the web actually works.
What would you choose?