Skip to content

· 6 min read

Next.js as a backend: Server Actions, Route Handlers or a separate Node.js API?

Do you need Express or NestJS next to your Next.js app? What each Next.js server feature is for, when a separate Node.js API is worth it, and a decision checklist for startups and full-stack teams.

Vishal Solanki · Full-stack developer, Noida

"Do we need a separate backend?" is one of the first questions on almost every new project I take on. Usually the honest answer is: not yet. Next.js can do most of what a small product's backend needs. But there are clear signs that a separate Node.js API is worth the extra moving parts, and it's much cheaper to spot them early.

This post covers what each server-side feature in Next.js is actually for, where each one falls short, and when to add Express, NestJS or another service next to it.

The four server tools in Next.js

ToolWhat it's forWho calls it
Server ComponentsReading data and rendering it to HTMLYour own pages, during render
Server ActionsMutations from your own UI: forms, buttonsYour own client code, over POST
Route Handlers (route.ts)Public HTTP endpoints that return anything: JSON, files, XMLAnyone: webhooks, mobile apps, partners, other services
Proxy (proxy.ts)Code that runs before a request: redirects, rewrites, headersEvery matching request

In Next.js 16, Middleware was renamed to Proxy. It does the same job; the new name says more clearly what it's for.

Reading data: go straight to the source

In a Server Component, read from the database or the upstream service directly, ideally through one data access layer:

// app/orders/page.tsx
import { getOrdersFor } from "@/lib/data/orders";
import { getSession } from "@/lib/auth";

export default async function Orders() {
  const session = await getSession();
  const orders = await getOrdersFor(session.userId);
  return <OrderList orders={orders} />;
}

A common mistake is calling your own Route Handler from a Server Component, like fetch("/api/orders"). That adds an HTTP round trip to your own server, and for pages pre-rendered at build time it fails outright, because there's no server running during the build. The Next.js docs call this out explicitly.

Mutations from your own UI: Server Actions

For forms and buttons in your app, Server Actions are the simplest option: no endpoint to name, progressive enhancement for forms, and you can revalidate the page in the same round trip.

Two things to know:

  • They're public POST endpoints. Check the session, ownership and input inside every action. My Server Actions security checklist covers this step by step.
  • They're queued. Actions from the same client run one after another. That's right for mutations, but it makes them a poor choice for fetching data.

Public endpoints: Route Handlers

Reach for a Route Handler when something other than your own UI needs to talk to your app:

  • Webhooks: payment providers, CRMs and messaging tools post events to a URL you give them.
  • Other clients: a mobile app, a partner integration or an internal tool that needs JSON.
  • OAuth and payment callbacks, where a third party redirects the user back to you.
  • Non-HTML responses: RSS, CSV exports, generated files.
// app/api/webhooks/payments/route.ts
export async function POST(request: Request) {
  const body = await request.text();
  const signature = request.headers.get("x-signature");
  if (!verifySignature(body, signature)) return new Response("Invalid signature", { status: 401 });

  const event = JSON.parse(body);
  await handlePaymentEvent(event); // idempotent: the provider may retry
  return new Response(null, { status: 204 });
}

Treat every Route Handler like any public API: verify signatures or tokens, validate the payload, rate-limit, and never put a mutation behind GET.

Before the request: Proxy

Proxy is good at cheap, request-level decisions: redirecting old URLs, rewriting a path, adding security headers, sending signed-out users to the login page early. It is not a complete auth layer. The Next.js docs are explicit about this: still verify the session where the data is read or changed, because a misconfigured matcher can let a request through.

When a separate Node.js API is worth it

Next.js on its own starts to strain when one or more of these is true:

  1. Several clients share the same business logic. A web app, a mobile app and partner integrations all need the same rules. A standalone API, documented and versioned, keeps those rules in one place and lets each client move at its own pace.
  2. You have long-running or background work. Queues, scheduled jobs, large imports, PDF generation, anything that runs for minutes. Serverless functions have execution time limits, and request handlers aren't a good home for a job runner anyway.
  3. You need persistent connections. WebSockets and long-lived streams for chat, live tracking or collaborative editing need a server that keeps connections open, or a managed real-time service.
  4. The work is CPU-heavy. Image or video processing and heavy calculations shouldn't slow down page rendering.
  5. Teams or scaling are separate. A dedicated backend team, separate deploys, or very different traffic patterns between the website and the API.
  6. Compliance requires it. Some industries need sensitive processing in a specific network, region or audited service.

If none of these apply, a separate API mostly adds a second deploy, a second set of logs, CORS settings and an extra network hop.

The middle path: Next.js as the backend for your frontend

You don't have to choose all or nothing. A common, solid setup:

  • Core services (Node.js with Express or NestJS, or anything else) own the business logic, the database and the background jobs.
  • Next.js is the backend for the frontend. Server Components read from those services. Server Actions call them for mutations. Route Handlers expose only what outside clients need.
  • The browser never talks to the core services directly. Tokens and service credentials stay on the Next.js server, and the user's session lives in an httpOnly cookie.

This is roughly how I approach sensitive flows on real products: moving operations that used to run in the browser onto the server, behind secure cookies, so tokens and business rules are never exposed to client-side JavaScript.

Keep one data access layer either way

Whichever setup you choose, put data access behind one server-only module and call it from Server Components, Server Actions and Route Handlers alike:

src/
  app/
    orders/page.tsx                 ← reads via lib/data
    orders/actions.ts               ← "use server", thin, calls lib/data
    api/webhooks/payments/route.ts  ← public endpoint, calls lib/data
  lib/
    auth.ts                         ← getSession()
    data/
      orders.ts                     ← import "server-only"; queries + permission checks

If you later move to a separate API, only lib/data changes: it calls the API instead of the database. The pages, actions and handlers stay as they are.

A decision checklist

QuestionIf yes
Is the only client your Next.js web app?Start with Next.js alone
Will a mobile app or partners need the same logic soon?Plan a standalone API, or at least a clean data layer you can move
Any jobs that run longer than a normal request?Add a worker or queue service
Real-time features (chat, live updates)?Add a WebSocket server or a managed real-time service
Heavy CPU work?Separate service
Separate backend team or very different scaling needs?Separate API
None of the above?Next.js alone; revisit when one becomes true

FAQ

Can Next.js replace Express?

For many web apps, yes. Server Components, Server Actions and Route Handlers cover reading, writing and public endpoints. Express or NestJS still wins for long-running jobs, persistent connections, multiple clients sharing one versioned API, and teams that want the backend deployed separately.

Should I build REST endpoints for my own frontend?

Usually not. Read data in Server Components and write with Server Actions. Build Route Handlers when a client other than your own UI needs them.

Is a Next.js backend production-ready?

Yes, with the same discipline as any backend: validation, authentication and authorisation on every entry point, rate limits on public ones, logging and monitoring, and keeping the framework patched.


Planning a new product, or untangling an existing one? I design and build both sides, from interface to API. Tell me what you're building.

  • Next.js
  • Node.js
  • Architecture
  • Full-stack