Praktična monorepo arhitektura sa Next.js, Fastify, Prisma i NGINX

Istražite praktičnu monorepo arhitekturu koristeći Next.js, Fastify, Prisma i NGINX, ističući integraciju i tok rada iz stvarnog sveta.
Published:
Aleksandar Stajić
Updated: 20. фебруар 2026. 21:41
Praktična monorepo arhitektura sa Next.js, Fastify, Prisma i NGINX

Illustration

Platformski Monorepo: Next.js (Javni + Admin) + Fastify API + Prisma DB + NGINX

Ovaj dokument opisuje trenutnu ciljnu arhitekturu za monorepo sa javnim Next.js sajtom, Next.js admin aplikacijom sa sesijama zasnovanim na kolačićima, Fastify API-jem i DB paketom zasnovanim na Prismi. Napisan je za članove tima koji će raditi sa više paketa i potrebne su im predvidive granice.

Ograničenja o kojima ne pregovaramo (za sada)

  • Javni sajt mora biti SSR (Next.js App Router). Bez prečica samo za SPA.
  • Javni sajt komunicira sa bekendom samo putem javnih API ruta pod /api (npr. /api/content, /api/media).
  • Admin koristi autentifikaciju sesije zasnovanu na kolačićima. HTTP-only kolačić. Bez tokena u localStorage-u.
  • API je Fastify. Sesije putem @fastify/session. „fakeRedis“ skladište za razvoj; Redis u produkciji kasnije.
  • Pristup bazi podataka je samo putem @platform/db. Bez direktnih Prisma klijenata razbacanih po aplikacijama.
  • NGINX terminira TLS i rutira /, /admin i /api. Jedan domen je u redu; poddomen je opcionalan kasnije.
  • Monitoring postoji od prvog dana (Prometheus scrape + Grafana dashboard placeholderi).

Raspored repozitorijuma

repo/
  apps/
    platform/        # public site (SSR)
    admin/           # admin UI (cookie session)
  packages/
    api/             # Fastify backend (auth/users/content/media)
    db/              # Prisma client + migrations
    shared/          # shared types, UI bits, utilities
  INFRASTRUCTURE/
    nginx/
      nginx.conf
      sites/
        app.conf
    monitoring/
      prometheus.yml
      grafana/
  docker-compose.yml
  turbo.json
  package.json

Kompromisi (pravi)

  • Sesije sa kolačićima su dosadne. To je funkcija. Rade iza proksija i drže logiku autentifikacije van frontenda. Loša strana: morate ispravno podesiti SameSite/Secure/path, inače ćete juriti „nasumične“ greške pri prijavi.
  • Jedna /api površina je čista, ali to takođe znači da morate biti strogi u pogledu toga šta je javno, a šta samo za administratore. Ne propuštajte administratorske krajnje tačke samo zato što je to zgodno.
  • Prisma može biti „spremna za više baza podataka“ u smislu zamene provajdera. Nije magični most između SQL-a i Mongo-a sa jednom šemom. Ako neko kaže da jeste, nije je isporučio.
  • Next.js SSR je dobar za SEO i prvo učitavanje. Takođe može preopteretiti API ako namerno ne podesite keširanje i revalidaciju.

Ugovor o rutiranju (NGINX kao ulazna vrata)

Za sada sve rutiramo kroz jedan domen. Jednostavnije je za debagovanje. Ako kasnije premestimo admin na poddomen, ponovo ćemo razmotriti opseg kolačića i pretpostavke o CSRF-u.

# INFRASTRUCTURE/nginx/sites/app.conf
server {
  listen 80;
  server_name yourdomain.com;

  location /api/ {
    proxy_pass http://api:4000/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
  }

  location /admin/ {
    proxy_pass http://admin:3001/;
    proxy_set_header Host $host;
  }

  location / {
    proxy_pass http://platform:3000/;
    proxy_set_header Host $host;
  }
}

API paket (Fastify) — sesije i autentifikacija

API poseduje autentifikaciju i stanje sesije. Aplikacije nikada ne kreiraju tokene. One samo šalju kolačiće nazad. Neka sadržaj sesije bude mali. ID korisnika + uloga. To je to.

// packages/api/src/server.ts
import Fastify from "fastify";
import cookie from "@fastify/cookie";
import session from "@fastify/session";
import { buildSessionStore } from "./sessionStore";
import { authRoutes } from "./routes/auth";
import { meRoutes } from "./routes/me";

export async function buildServer() {
  const app = Fastify({ logger: true });

  await app.register(cookie);
  await app.register(session, {
    secret: process.env.SESSION_SECRET || "dev-secret-change-me",
    cookieName: "sid",
    cookie: {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "lax",
      path: "/"
    },
    store: buildSessionStore(),
    saveUninitialized: false
  });

  app.register(authRoutes, { prefix: "/api" });
  app.register(meRoutes, { prefix: "/api" });

  return app;
}

// packages/api/src/index.ts
import { buildServer } from "./server";

(async () => {
  const app = await buildServer();
  const port = Number(process.env.PORT || 4000);
  await app.listen({ port, host: "0.0.0.0" });
})();
// packages/api/src/sessionStore.ts
import type { SessionStore } from "@fastify/session";

export function buildSessionStore(): SessionStore {
  const mem = new Map<string, { value: any; expiresAt: number }>();

  return {
    get: (sid, cb) => {
      const hit = mem.get(sid);
      if (!hit) return cb(null, null);
      if (Date.now() > hit.expiresAt) {
        mem.delete(sid);
        return cb(null, null);
      }
      cb(null, hit.value);
    },
    set: (sid, session, cb) => {
      const ttlMs = 1000 * 60 * 60 * 8;
      mem.set(sid, { value: session, expiresAt: Date.now() + ttlMs });
      cb(null);
    },
    destroy: (sid, cb) => {
      mem.delete(sid);
      cb(null);
    }
  };
}

DB paket (Prisma) — jedan klijent, deljeni tipovi

Izlažemo jedan PrismaClient iz @platform/db. API uvozi to i samo to. Ako kreirate drugi Prisma klijent u aplikaciji jer je „brže za prototipovanje“, samo stvarate budući incident.

// packages/db/src/index.ts
import { PrismaClient } from "@prisma/client";

export const db = new PrismaClient();

Admin aplikacija — sesija sa kolačićima i /me zaštita

Admin je normalna Next.js App Router aplikacija. Jedini poseban deo je da mora tretirati API kao izvor istine i uvek uključivati kolačiće prilikom pozivanja /api/me.

// apps/admin/app/(admin)/layout.tsx
import { cookies } from "next/headers";

async function getMe() {
  const cookieHeader = cookies().toString();
  const res = await fetch(`${process.env.ADMIN_BASE_URL}/api/me`, {
    headers: { cookie: cookieHeader },
    cache: "no-store"
  });
  return res.json();
}

export default async function AdminLayout({ children }: { children: React.ReactNode }) {
  const me = await getMe();
  if (!me?.ok) {
    return (
      <html>
        <body>
          <p>Unauthorized. Go to /admin/login.</p>
        </body>
      </html>
    );
  }

  return (
    <html>
      <body>{children}</body>
    </html>
  );
}

Korak po korak: tok prijave administratora

  1. Pretraživač šalje akreditive: POST /api/login
  2. NGINX prosleđuje /api/* na Fastify
  3. Fastify validira korisnika u odnosu na DB (Prisma)
  4. Fastify upisuje sesiju u fakeRedis skladište (dev) i vraća Set-Cookie: sid=…
  5. Pretraživač čuva kolačić (HTTP-only)
  6. Admin serverske komponente pozivaju GET /api/me sa priloženim kolačićem
  7. Admin prikazuje kontrolnu tablu samo ako /me vrati ok=true

Jedna stvar koja je pošla naopako (da je ne ponavljamo)

Imali smo petlju za prijavu iako je /api/login vratio 200. Kolačić nikada nije poslat nazad na /api/me. Uzrok je bio dosadan: neusklađenost putanje kolačića + rutiranja proksija. Slučajno smo postavili kolačić za /admin, ali /api/me živi pod /api. Pretraživač je uradio tačno ono što je trebalo. Nije poslao kolačić. Rešenje je bilo da se putanja kolačića postavi na „/“ i potvrdi da NGINX nije prepisivao putanje na način koji bi to pokvario.

Plan projekta (prvo isporuka, kasnije dorada)

  1. Faza 0: povezivanje monorepoa (Turbo, TS konfiguracija, uvoz deljenih paketa). Neka bude jednostavno.
  2. Faza 1: API krajnje tačke za autentifikaciju/sesiju: /login, /logout, /me. Dodati osnovno ograničenje stope na /login.
  3. Faza 2: Admin MVP: ekran za prijavu + serverska zaštita + minimalna ljuska kontrolne table.
  4. Faza 3: Krajnje tačke za čitanje sadržaja za javnu platformu: /api/content/* (prvo samo za čitanje).
  5. Faza 4: CRUD sadržaja u adminu + otpremanje medija (početi jednostavno, zatim kasnije zameniti skladište).
  6. Faza 5: Nadzor i učvršćivanje: Prometheus krajnja tačka za metrike, Grafana kontrolne table, zaglavlja, rezervne kopije.

Definicija završenog (da se kasnije ne bismo svađali)

  • Novi pretraživač se može prijaviti na /admin i ostati prijavljen tokom osvežavanja.
  • Javna platforma prikazuje SSR sadržaj sa /api/content bez izlaganja krajnjih tačaka samo za administratore.
  • NGINX ispravno rutira /, /admin, /api u docker-compose-u.
  • API se može restartovati bez narušavanja ponašanja sesije (resetovanja dev skladišta su u redu, ali mora graciozno da otkaže).
  • Postoji barem jedna Prometheus scrape meta (čak i ako su Grafana paneli placeholderi).

Ako implementirate novu krajnju tačku, prvo odlučite: da li je javna, samo za administratore ili interna. Ne nagađajte. Zatim je stavite iza odgovarajućeg prefiksa i zaštitite je. To je cela igra.