Full-stack vs API-only vs full-stack JS
Django/Rails (batteries included), Express/FastAPI (minimal), Next.js/Remix (full-stack JS). Each shape optimizes for a different product reality.
Batteries-included trade-off (Django/Rails): django-admin startproject gives you auth, admin UI for every model, ORM + migrations, forms, i18n, caching, session middleware, test client. Velocity for conventional CRUD is unmatched. Cost: (a) all components are coupled — upgrading Django drags 20+ opinionated pieces with it; (b) deviating from the golden path (GraphQL, event sourcing, CQRS) means fighting the framework; (c) framework abstractions (CBVs, Forms, ModelAdmin) are a significant learning curve themselves.
Minimal framework trade-off (FastAPI/Express): you assemble: FastAPI + SQLAlchemy + Alembic + Pydantic + Celery + python-jose (JWT) + Starlette middleware = DIY-Django. Each piece evolves independently, you pick best-of-breed. Cost: (a) bootstrapping takes days (vs minutes); (b) you own the integration and security; (c) choice paralysis; (d) lower team consistency — two FastAPI projects can look radically different.
Full-stack JS architecture (Next.js App Router): server components render on the server and stream HTML; client components ("use client") hydrate for interactivity; server actions let client forms call typed server functions without defining REST endpoints. This collapses the REST API boilerplate FOR THE PRODUCT'S OWN UI. Types flow seamlessly from DB schema (Prisma/Drizzle) → server → client. The catch: any non-web client still needs a proper API.
BFF (Backend For Frontend) pattern: one backend per frontend, shaped for that UI's exact needs (denormalized responses, stitched data across services). Next.js + route handlers is a natural BFF: your UI talks to its own server layer, which talks to 'real' backend services. Avoids REST's over-fetching / under-fetching when the UI has specific needs.
Coupling cost of full-stack JS: the same codebase handles client rendering, server rendering, server actions, and server data fetching. Benefits: shared types, single deployment, no network boundary for first-party UI. Costs: client and server bundles must be separable (import fs in a client component = build error); framework complexity increases ("use client" directive, streaming boundaries, caching semantics); tightly couples UI velocity to server deploy cadence.
Admin / back-office as a decision factor: Django's admin is a killer feature — CRUD over any model in minutes. Rails has ActiveAdmin. Next.js has no equivalent; you either build it (significant effort) or add react-admin, refine, or a tool like Retool for internal work. For SaaS products with heavy back-office needs, admin tooling can drive the framework choice alone.
Decision rubric: (a) product = public web + mobile + partners → API-only backend + SPA + native apps. (b) product = content-heavy SEO-first marketing + app → Next.js or Django + Django-CMS. (c) product = internal tool with lots of CRUD → Django/Rails. (d) product = real-time collaborative app → Phoenix + LiveView, Next.js + Liveblocks, or custom WebSocket server. Most shops mix: a Next.js web app + a shared FastAPI/Django API for long-term data.
Grounded on https://www.djangoproject.com/start/overview/
Next up
Request lifecycle — the shape they all share
Every framework does the same dance: parse → route → middleware → handler → serialize → respond. Learn the shape once, read any framework faster.