Практическая архитектура монорепозитория с Next.js, Fastify, Prisma и NGINX

Исследуйте практическую архитектуру монорепозитория с использованием Next.js, Fastify, Prisma и NGINX, подчеркивающую реальную интеграцию и рабочий процесс.
Опубликовано:
Aleksandar Stajić
Updated: 20 февраля 2026 г. в 21:41
Практическая архитектура монорепозитория с Next.js, Fastify, Prisma и NGINX

Монорепозиторий платформы: Next.js (Публичный + Админ) + Fastify API + Prisma DB + NGINX

Этот документ описывает текущую целевую архитектуру монорепозитория с публичным сайтом на Next.js, административным приложением на Next.js с сессиями на основе куки, Fastify API и пакетом базы данных на основе Prisma. Он написан для членов команды, которые будут работать с несколькими пакетами и нуждаются в предсказуемых границах.

Ограничения, которые мы не обсуждаем (пока)

  • Публичный сайт должен быть SSR (Next.js App Router). Никаких сокращений только для SPA.
  • Публичный сайт взаимодействует с бэкендом только через публичные маршруты API по адресу /api (например, /api/content, /api/media).
  • Админ использует аутентификацию на основе куки-сессий. Куки только для HTTP. Никаких токенов в localStorage.
  • API — Fastify. Сессии через @fastify/session. Хранилище «fakeRedis» для разработки; Redis в продакшене позже.
  • Доступ к БД осуществляется только через @platform/db. Никаких прямых клиентов Prisma, разбросанных по приложениям.
  • NGINX завершает TLS и маршрутизирует /, /admin и /api. Один домен подходит; поддомен необязателен позже.
  • Мониторинг существует с первого дня (сбор данных Prometheus + заглушки для дашбордов Grafana).

Структура репозитория

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

Компромиссы (реальные)

  • Куки-сессии скучны. Это особенность. Они работают за прокси и удерживают логику аутентификации вне фронтенда. Недостаток: вы должны правильно настроить SameSite/Secure/path, иначе будете гоняться за «случайными» ошибками входа.
  • Единый интерфейс /api чист, но это также означает, что вы должны строго разделять публичные и только для администратора эндпоинты. Не допускайте утечки административных эндпоинтов просто потому, что это удобно.
  • Prisma может быть «готовой к работе с несколькими БД» в смысле замены провайдеров. Это не волшебный мост между SQL и Mongo с единой схемой. Если кто-то говорит, что это так, они еще не выпустили продукт.
  • Next.js SSR хорош для SEO и первой загрузки. Он также может перегрузить API, если вы намеренно не настроите кеширование и ревалидацию.

Контракт маршрутизации (NGINX как входная дверь)

Пока мы маршрутизируем все через один домен. Это проще для отладки. Если позже мы перенесем админку на поддомен, мы пересмотрим область действия куки и предположения о CSRF.

# 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 (Fastify) — сессии и аутентификация

API владеет аутентификацией и состоянием сессии. Приложения никогда не выпускают токены. Они просто отправляют куки обратно. Сохраняйте полезную нагрузку сессии небольшой. ID пользователя + роль. Вот и все.

// 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);
    }
  };
}

Пакет БД (Prisma) — один клиент, общие типы

Мы предоставляем один PrismaClient из @platform/db. API импортирует только его. Если вы создадите второй клиент Prisma в приложении, потому что это «быстрее для прототипирования», вы просто создадите будущий инцидент.

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

export const db = new PrismaClient();

Админ-приложение — куки-сессия и защита /me

Админ — это обычное приложение Next.js App Router. Единственная особенность заключается в том, что оно должно рассматривать API как источник истины и всегда включать куки при вызове /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>
  );
}

Пошагово: процесс входа администратора

  1. Браузер отправляет учетные данные: POST /api/login
  2. NGINX перенаправляет /api/* на Fastify
  3. Fastify проверяет пользователя по БД (Prisma)
  4. Fastify записывает сессию в хранилище fakeRedis (разработка) и возвращает Set-Cookie: sid=…
  5. Браузер сохраняет куки (только HTTP)
  6. Серверные компоненты админки вызывают GET /api/me с прикрепленным куки
  7. Админка отображает дашборд только если /me возвращает ok=true

Одна вещь, которая пошла не так (чтобы мы не повторяли)

У нас был цикл входа, хотя /api/login возвращал 200. Куки никогда не отправлялись обратно на /api/me. Причина была скучной: несоответствие пути куки и маршрутизации прокси. Мы случайно установили куки для /admin, но /api/me находится по адресу /api. Браузер сделал именно то, что должен был. Он не отправил куки. Решение заключалось в установке пути куки на «/» и подтверждении того, что NGINX не переписывал пути таким образом, чтобы это нарушало работу.

План проекта (сначала выпуск, потом доработка)

  1. Фаза 0: настройка монорепозитория (Turbo, TS config, импорты общих пакетов). Сохраняйте его минималистичным.
  2. Фаза 1: Эндпоинты аутентификации/сессий API: /login, /logout, /me. Добавить базовое ограничение скорости на /login.
  3. Фаза 2: MVP админки: экран входа + серверная защита + минимальная оболочка дашборда.
  4. Фаза 3: Эндпоинты для чтения контента для публичной платформы: /api/content/* (сначала только чтение).
  5. Фаза 4: CRUD контента в админке + загрузка медиа (начните с простого, затем замените хранилище).
  6. Фаза 5: Мониторинг и усиление безопасности: эндпоинт метрик Prometheus, дашборды Grafana, заголовки, резервные копии.

Определение готовности (чтобы потом не спорить)

  • Новый браузер может войти в /admin и оставаться авторизованным при обновлениях страницы.
  • Публичная платформа отображает SSR контент из /api/content, не раскрывая эндпоинты только для администратора.
  • NGINX корректно маршрутизирует /, /admin, /api в docker-compose.
  • API может перезапускаться без нарушения поведения сессии (сбросы хранилища разработки допустимы, но он должен завершаться корректно).
  • Существует хотя бы одна цель сбора метрик Prometheus (даже если панели Grafana являются заглушками).

Если вы реализуете новый эндпоинт, сначала решите: он публичный, только для администратора или внутренний. Не гадайте. Затем поместите его за правильный префикс и защитите. В этом вся суть.

Related Articles

Мульти-базовая архитектура с Prisma 7: Глубокое погружение для экспертов

Мульти-базовая архитектура с Prisma 7: Глубокое погружение для экспертов

Управление сложными ландшафтами данных требует современных архитектур. Prisma 7 предлагает расширенные функции для интеграции с несколькими базами данных и решает проблемы полиглотной персистентности.

OpenAI Agents API против Agents SDK против Responses API: на чем вам стоит разрабатывать в 2026 году?

OpenAI Agents API против Agents SDK против Responses API: на чем вам стоит разрабатывать в 2026 году?

Стек агентов OpenAI изменился в сентябре 2026 года. Это архитектурное руководство разделяет Agents API, Agents SDK, Responses API и Codex SDK по владению средой выполнения — чтобы команды могли выбрать правильную границу контроля вместо сравнения названий продуктов.

Мультитенантная архитектура корпоративного уровня для международной платформы

Мультитенантная архитектура корпоративного уровня для международной платформы

Loving Rocks является корпоративной свадебной платформой, разработанной с истинной многоарендной архитектурой, изолированными базами данных для каждого арендатора и встроенной интернационализацией для глобальной масштабируемости, безопасности и долгосрочной операционной стабильности.