Building Microservices and Modular Fullstack Apps with Next.js

Table of Contents

  1. Introduction
  2. Why Modular and Microservice Architectures?
  3. Monorepo Setup with Turborepo
  4. Structuring Multiple Apps and Shared Packages
  5. Decoupling Frontend and Backend Logic
  6. Using tRPC for Type-Safe APIs
  7. GraphQL as a Universal Interface
  8. Direct DB Access in Server Components
  9. Sharing Logic and UI Across Services
  10. Authentication and Authorization Strategies
  11. Deployment Strategies for Multi-App Systems
  12. Conclusion

1. Introduction

Modern web apps are rarely monoliths. As your codebase grows, modular architecture and microservices help you:

  • Keep features isolated
  • Enable independent deployments
  • Reuse shared components
  • Scale teams and systems more efficiently

With Next.js (App Router) and tools like Turborepo, tRPC, GraphQL, and Prisma, you can build a production-grade microservice-oriented fullstack app inside a monorepo.


2. Why Modular and Microservice Architectures?

Benefits include:

  • Separation of Concerns: Different teams can work on different parts (auth, billing, UI).
  • Better CI/CD: Smaller units = faster builds.
  • Independent Scaling: Backend services can scale independently from frontend apps.
  • Shared Logic and UI: Use shared packages for validation, types, UI components, etc.

3. Monorepo Setup with Turborepo

Use Turborepo to manage monorepos efficiently.

bashCopyEditnpx create-turbo@latest

Directory structure:

vbnetCopyEditapps/
  web/           → Next.js frontend
  admin/         → Another Next.js app
  api/           → Express or custom backend
packages/
  ui/            → Reusable UI components
  config/        → Shared Tailwind config
  utils/         → Shared helpers, zod schemas, types

turbo.json defines the build pipeline per app/package.


4. Structuring Multiple Apps and Shared Packages

Apps: Each folder under apps/ is an independently deployable app.

Packages: Shared code lives here. This can include:

  • ui (React components, buttons, inputs)
  • types (TypeScript interfaces, zod schemas)
  • lib (API clients, database helpers)

You can import like so:

tsCopyEditimport { Button } from '@myorg/ui';
import { userSchema } from '@myorg/types';

Configure tsconfig.json paths and set up project references.


5. Decoupling Frontend and Backend Logic

You can decouple by:

  • Creating a separate api/ backend app using Express, NestJS, or even another Next.js app
  • Using an RPC layer like tRPC or GraphQL
  • Making backend functions callable via server components

6. Using tRPC for Type-Safe APIs

tRPC lets you create type-safe API layers with zero client/server code duplication.

Setup:

In packages/trpc:

tsCopyEdit// trpc/router.ts
import { initTRPC } from '@trpc/server';

const t = initTRPC.create();

export const appRouter = t.router({
  greeting: t.procedure.query(() => 'Hello from tRPC!'),
});

export type AppRouter = typeof appRouter;

In frontend (SSR/server components):

tsCopyEdit// web/app/api/trpc/[trpc].ts
import { appRouter } from '@myorg/trpc';
import { createNextApiHandler } from '@trpc/server/adapters/next';

export default createNextApiHandler({ router: appRouter });

Client-side usage:

tsCopyEditconst { data } = trpc.greeting.useQuery();

7. GraphQL as a Universal Interface

For more flexibility and API federation, you can opt for GraphQL:

  • Use Apollo Server in your backend.
  • Use urql or Apollo Client in your frontend.
  • Design a modular schema with multiple resolvers for user, product, orders, etc.

Example:

tsCopyEditquery {
  user(id: "1") {
    name
    email
  }
}

GraphQL also supports batching, subscriptions, and granular access control.


8. Direct DB Access in Server Components

You can skip the API altogether in some cases and access the database directly in server components.

With Prisma:

tsCopyEdit// app/page.tsx (server component)
import { db } from '@myorg/db';

export default async function Home() {
  const users = await db.user.findMany();
  return <UserList users={users} />;
}

This is performant and avoids unnecessary API calls — especially useful for internal tools and admin panels.


9. Sharing Logic and UI Across Services

Use packages like:

  • @myorg/ui: Button, Modal, Input
  • @myorg/validators: Shared zod schemas
  • @myorg/db: Prisma client and models
  • @myorg/auth: Shared auth logic (e.g., JWT verification)

Enable TypeScript project references so everything compiles properly.


10. Authentication and Authorization Strategies

Each app can share auth logic from @myorg/auth, which includes:

  • JWT signing/verification
  • Role-based access checks
  • Middleware or higher-order components for route protection

You can even move common logic to server-only functions in shared packages.


11. Deployment Strategies for Multi-App Systems

Options:

  • Vercel: Best for deploying multiple Next.js apps.
  • Railway / Render: Great for deploying backend services.
  • Docker Compose or Kubernetes: For advanced infra or self-hosted setups.
  • Turborepo Remote Caching: Speeds up builds across CI/CD pipelines.

Each app can be deployed independently with its own CI pipeline.


12. Conclusion

Building modular fullstack apps and microservices with Next.js is no longer complicated. With App Router, server components, tRPC, GraphQL, and Prisma, you can decouple layers while keeping DX high and performance tight.

A solid monorepo setup ensures scalable development across multiple teams, apps, and services — without duplicating code.