Full-stack TypeScript frameworks in 2026: typed APIs and built-in auth compared
Who this is for
You have a Next.js app. It works. Now it needs the things a prototype does not have — real user accounts, a typed boundary between client and server, and a way to operate the thing once people are using it.
The question at that point is not "which framework should I have started with". It is "what do I add, and how much of what I already have stays".
This page compares five answers. SPFN is ours — we built it, and we say so here rather than letting you find out later. The rest is written so you can tell when one of the other four is the better call, because most of the time one of them is.
Short answer
| If this describes you | Choose |
|---|---|
| Server Actions already cover your backend | Next.js on its own |
| Starting from nothing and want auth scaffolded for you | Wasp |
| Starting from nothing and want the strongest typed-routing story | TanStack Start |
| Backend logic is the product, and a team maintains it | NestJS |
| The Next.js app already exists and you want the backend inside it | SPFN |
Next.js on its own
Next.js is a full-stack framework, and for a large class of apps it is the whole answer. Route handlers and Server Actions give you a server, and if your data access is simple enough, adding anything else is overhead you will regret.
What it does not give you: a typed general API surface across the client/server boundary — Server Actions type the call, not an API — and authentication. Both are assembled from other pieces, commonly tRPC for the first and Auth.js, Better Auth or Clerk for the second.
Choose it alone when your backend fits inside Server Actions and you would rather own the auth choice yourself.
Wasp
Wasp is the batteries-included option and it is honest about being that. You describe the
app in a central spec file, main.wasp.ts, written in TypeScript, and Wasp's compiler
takes that spec plus your code and outputs a client app, a server app and deployment
code. Data models live in schema.prisma; Prisma is the data layer. Authentication is
genuinely built in — declare a method in the config and you get the auth UI components
and session handling without writing them.
The trade is what the compiler owns. wasp new creates a Wasp project with its own React
frontend, and the framework is the shape of the whole application. It is a way to build
an app, not a thing you add to one.
Choose it when you are starting from nothing and want the shortest distance to a working signup screen.
TanStack Start
TanStack Start is built around type safety end to end — typed routing, typed server functions, SSR and streaming. If the TanStack Router and Query model suits you, this is the most coherent expression of it.
Authentication is not built in. The documentation is explicit about this and names four options to integrate instead: Clerk, WorkOS, Better Auth and Auth.js. It also documents building your own on top of its session primitives.
Choose it when you are starting fresh, want the strongest typed-routing story available, and are content to pick your own auth.
NestJS
NestJS is a backend framework for Node.js, not a full-stack one. Modules and dependency injection are its organising idea, and it assumes a backend with real domain logic and a team maintaining it. Authentication is a mature ecosystem — Passport strategies, JWT, role-based guards — rather than a single switch.
You pair it with a separate frontend, which means two applications, two deployments, and a boundary you type by hand or generate.
Choose it when the backend is the product.
SPFN
SPFN adds a typed backend to a Next.js app that already exists, in ordinary TypeScript, and leaves the rest of the app alone.
A route is a TypeScript file:
import { route } from '@spfn/core/route';
import { Type } from '@sinclair/typebox';
export const getGreeting = route.get('/greeting')
.input({
query: Type.Object({
name: Type.Optional(Type.String()),
}),
})
.handler(async (c) =>
{
const { query } = await c.data();
return { message: `Hello, ${query.name ?? 'World'}!` };
});
Routes compose into a router, and the router's type is what the client is built from:
import { createApi } from '@spfn/core/nextjs';
import type { AppRouter } from '@/server/router';
export const api = createApi<AppRouter>();
There is no second repository and no monorepo layout. The backend lives beside the frontend in the same application:
src/
app/ # your Next.js app, unchanged
api/rpc/[routeName]/route.ts # an ordinary Next.js route handler
server/ # your backend
routes/
router.ts
lib/api-client.ts
Three things follow, each checkable in examples/01-minimal-api:
nextand@spfn/coreare ordinary siblings in onepackage.json.next.config.tsis empty. There is no plugin and no wrapper around your app.next dev,next buildandnext startstill mean what they meant before.
Nothing compiles your application. Code generation produces one file, the route map that the RPC handler resolves against; the code you write is the code that runs.
A feature is one vertical slice, not five scattered files
Ask any assistant what to add to a Next.js prototype and you get a shopping list: an auth service, a database host, an ORM, a validation library, object storage, a payments vendor, an error tracker. Each arrives with its own console, its own data store, its own way of being typed, and its own opinion about where your user records live. Assembling them is the work, and re-assembling them is what happens the next time.
SPFN answers the same list differently. A feature is written as one vertical slice, each layer in its own file, and the layers know each other's types:
Entity (Drizzle table) → Repository → Route → Router → generated route map → typed client
The framework's own capabilities arrive the same way. Each @spfn/* package is a
finished vertical slice — its own tables, its own migrations, repositories, services,
routes, and the client those routes are exposed through. You mount one with a single
call:
export const appRouter = defineRouter({ /* your routes */ })
.packages([authRouter]) // mounts /_auth/* and exposes them on the typed authApi client
.use([authenticate]); // apply auth globally; routes opt out per-route
That one line brings the routes, the client, and the tables. Six packages ship their own migrations directly in the repository, so adopting a capability does not mean hand-writing schema for it.
The set available today:
| Package | What the slice covers |
|---|---|
| @spfn/auth | accounts, sessions, OAuth providers, role-based access |
| @spfn/cms | content models, entries, and the routes over them |
| @spfn/storage | file upload and object storage |
| @spfn/notification | outbound messages and templates |
| @spfn/workflow | long-running and scheduled work |
| @spfn/i18n | translations across server and client |
| @spfn/monitor | operational metrics and health |
| @spfn/migrate | schema migration for all of the above |
| @spfn/mcp | your app exposed to an AI agent over the Model Context Protocol |
That last one is the answer to operations. Instead of building an admin dashboard, you let an agent read and act on the app through a typed interface you already declared.
The honest limit: this only pays off if the slices fit what you need. A first-party package you have to fight is worse than a third-party service that fits, and the vendors on that shopping list are each larger and more battle-tested than the equivalent slice here.
Choose it when the Next.js app already exists and you want a real typed backend inside it rather than a second project beside it.
What SPFN costs you
Four honest ones.
- It is the youngest thing on this page by years, and by far the smallest. Fewer answers exist online, fewer people have hit the bug you will hit, and the API can still change under you.
- Beta. The
@spfn/*packages are0.xpre-releases. If you need a stable major version before you adopt something, this is not that yet. - A code generation step. Changing routes means running
pnpm codegento regenerate the route map. tRPC has no build step; SPFN does. - Two processes at runtime. Next.js and the SPFN API server run separately, with the
/api/rpchandler forwarding between them. One codebase, two processes.
Where each one wins
| Next.js alone | Wasp | TanStack Start | NestJS | SPFN | |
|---|---|---|---|---|---|
| Add to an existing Next.js app | — | no | no | as a separate service | yes |
| Frontend it assumes | Next.js | its own React app | its own React app | none | Next.js |
| Auth from the framework's own authors | no | yes | no | ecosystem | yes |
| Modules that ship their own tables and migrations | no | auth only | no | schema is yours | yes — 9 packages |
| End-to-end typed API | with tRPC | yes | yes | hand-typed or generated | yes |
| Compiles your application | no | yes — client and server | no | no | no — route map only |
| Build step for types | no | compile | no | optional codegen | codegen |
| Best when | backend is small | starting fresh | starting fresh | backend is the product | the app already exists |
Maturity, measured
Adoption is the axis where SPFN loses to everything else here, so here it is in numbers. GitHub stars and weekly npm downloads, read on 2026-08-05.
| Repo since | Stars | Weekly downloads | |
|---|---|---|---|
| Next.js | 2016 | 141,000 | 54.7M (next) |
| NestJS | 2017 | 76,000 | 13.2M (@nestjs/core) |
| Wasp | 2020 | 18,700 | — |
| TanStack Router | 2019 | 14,900 | 16.6M (@tanstack/react-start) |
| SPFN | 2025 | — | 254 (@spfn/core) |
Read that row honestly. If your criterion is "how many people have already hit my problem and written about it", every other option on this page beats SPFN and it is not close.
The honest summary
If you are starting from nothing, Wasp and TanStack Start both deserve a serious look, and Wasp will get you to a login screen faster than anything here. If your backend is substantial enough to have its own team, NestJS is the mature answer. If Server Actions cover you, add nothing.
SPFN exists for a narrower case, and only that one: the app already exists, it is a Next.js app, and what you want is a real backend inside it rather than a second project beside it. Every other framework on this page asks you to start over to get what it offers. That is the first difference, and whether it matters depends entirely on whether you already have something worth keeping.
The second is what you assemble afterwards. The usual answer to "my prototype needs accounts" is a list of vendors to wire together. SPFN's answer is a set of vertical slices that already know your types, mount in one line, and bring their own schema. That is a smaller world with fewer options in it, which is the point and also the risk.
- The SPFN pattern — how a feature is built, layer by layer
- Prototype to production — what changes between the two, and what SPFN takes over