Why We Built 5 Products on FastAPI + Next.js (and Would Do It Again)

8 min read

The Stack Decision That Shapes Everything

Choosing a tech stack when you are a small team is one of those decisions that feels reversible but really is not. Sure, you can theoretically rewrite everything later. But in practice, whatever you pick on week one is what you are building on for the next two years. The frameworks you choose determine how fast you ship, how easy it is to hire (or onboard friends), and how much time you spend fighting your tools instead of building features.

We are a group of friends from Tennessee building SaaS products under Obsidian Clad Labs. We have shipped five products on the same stack: FastAPI on the backend, Next.js on the frontend, PostgreSQL for the database. After a year of building, here is why we would make the same choice again, and what we would do differently.

Why FastAPI for the Backend

FastAPI is a Python web framework built on top of Starlette and Pydantic. It is async by default, has automatic OpenAPI documentation, and uses Python type hints for request/response validation. The learning curve from knowing basic Python to building production APIs is remarkably short.

For us, the biggest win is the Python ecosystem. Several of our products use machine learning models, natural language processing, and data analysis. Python is the language where all of that tooling lives. We can import scikit-learn, use sentence-transformers, call into PyTorch models, and process data with pandas — all in the same codebase that serves our API endpoints. No microservice boundary, no inter-process communication, no separate ML service. Just Python all the way down.

The auto-generated docs are more useful than they sound. Every endpoint we write automatically shows up in a Swagger UI at /docs. During development, we use this to test endpoints without writing a frontend first. During integration, our frontend developer uses it as a contract. It is living documentation that never goes stale because it is generated from the code itself.

The tradeoff is performance. Python is not the fastest language. For CPU-bound work, it is significantly slower than Go or Rust. But for IO-bound API work — which is most of what a SaaS backend does (database queries, external API calls, file operations) — FastAPI's async support handles it well. We have not hit a point where Python's speed was the bottleneck. The bottleneck is always the database or a third-party API.

Why Next.js for the Frontend

Next.js gives us React with server-side rendering, static generation, file-based routing, and a deployment story that is essentially one click on Vercel. For a small team, the time savings from not configuring webpack, not setting up a router, and not managing a CDN are enormous.

SEO is a real consideration for SaaS products. Your landing page, your blog, your pricing page — all of these need to be indexable by search engines. With a pure client-side React app, you are fighting an uphill battle. With Next.js, every page can be server-rendered or statically generated, which means Google sees the full content on first crawl. Our blog posts, help pages, and marketing content all benefit from this.

The React ecosystem is another advantage. Need a rich text editor? There are five good ones. Need a chart library? Plenty of options. Need a date picker that does not make you want to throw your laptop? React has you covered. When you are building multiple products, being able to reuse UI patterns and component libraries across all of them is a massive time saver.

The downside is complexity. Next.js has a lot of concepts: server components, client components, the app router, middleware, API routes, server actions. It is a lot to hold in your head, especially for someone coming from a simpler framework. We have had bugs that took hours to diagnose because we did not fully understand the server/client boundary. But the productivity gains outweigh the learning curve.

PostgreSQL: The Boring Choice That Just Works

We use PostgreSQL for every product. It is reliable, it is free, it has excellent tooling, and it scales further than any of our products currently need. We host it on Railway, which gives us a managed Postgres instance with backups and connection pooling for a few dollars a month.

We considered MongoDB early on because of the flexibility of schemaless documents. But after working with it on a previous project, we found that the lack of schema enforcement just moved the problem from the database to the application layer. With PostgreSQL and proper migrations, the database enforces data integrity and we spend less time debugging weird data.

The combination of PostgreSQL and FastAPI's async support (via asyncpg) is excellent. Database queries do not block the event loop, so a slow query in one request does not hold up every other request. It is one of those things that just works and you never think about, which is exactly what you want from your database layer.

Why This Stack Works for a Three-Person Team

The real advantage of this stack is velocity. We can go from idea to deployed product in days, not weeks. The backend and frontend are separate but the deployment story for both is push-to-deploy. Railway watches the backend repo and auto-deploys on push. Vercel does the same for the frontend.

Because we use the same stack across all five products, knowledge transfers perfectly. If one of us figures out how to implement Stripe webhooks in FastAPI, that pattern works identically in every product. If we build a reusable auth flow in Next.js, we can drop it into the next project. There is no context switching between languages, frameworks, or deployment pipelines.

We have also found that Python and TypeScript are a surprisingly good pairing for a small team. Both are dynamically-inspired but type-safe (Python with type hints and Pydantic, TypeScript by design). The mental model is similar enough that switching between backend and frontend code does not feel like switching languages entirely.

The Tradeoffs We Accept

No stack is perfect. Python's cold start time on serverless platforms is real. FastAPI on Railway takes a few seconds to spin up if the container has been idle. For a SaaS product with regular traffic this does not matter, but for a side project that gets one visitor a day, that first request can be slow.

Next.js version churn is something we deal with. The framework evolves fast and sometimes breaking changes slip through minor versions. We have learned to pin our versions and not upgrade until we have a reason to.

Python dependency management is its own special challenge. We use pip with requirements.txt and occasionally run into version conflicts between packages. A proper lock file tool like pip-tools or Poetry would be better, and it is on our list to standardize. But for now, pinned versions and manual testing get the job done.

These are tradeoffs we willingly accept because the alternative — spending months evaluating stacks, learning new frameworks, or over-engineering for scale we do not have — is worse. Velocity matters more than perfection when you are small. Ship the product, talk to users, iterate. The stack should serve that goal, not the other way around.

See what this stack can build

TeachShield is one of five products built on FastAPI + Next.js by our small Tennessee team. Try it for free.