Mriya Playground

Architecture · modular monolith

How It Works Under the Hood

One API, one database, shared contracts, and no service that is not needed. Here is how requests move, where the data lives and which trade-offs we chose on purpose.

API modules
5
HTTP and WebSocket endpoints
13
external services
3
deployable backend
1

The System

From the browser to the data: three layers and two shared packages.

Client

  • Browser

    running

    HTTP and WebSocket to the API; the contact form goes straight to EmailJS.

  • apps/web

    running

    Next.js 16 App Router, React 19, next-intl, TanStack Query.

  • Developer Console

    running

    Records the requests and events the app already makes, without secrets or personal data.

Server

  • apps/api

    running

    NestJS modular monolith on /api/v1, one module per demo.

    • health
    • analytics
    • realtime
    • workflows
    • ai-search
  • Cross-cutting

    running

    Zod validation, rate limiting, request ids, one error envelope, OpenAPI at /api/v1/docs.

Data & external services

  • PostgreSQL

    running

    Workflow runs, their steps and events; migrations on start-up.

  • In-memory dataset

    running

    Synthetic orders for analytics and AI search, generated once on start-up.

  • Binance · Bybit

    running

    Public BTC/USDT price streams over WebSocket, one connection per exchange.

  • EmailJS

    running

    Delivers the contact form straight from the browser.

  • Redis

    planned

    In the Docker stack, but no code uses it: a queue arrives only when one is needed.

packages/contractsZod schemas and types for web and api
packages/uiA self-contained UI component library
A green mark means the component is running. A dashed outline means it is in the stack but no code uses it yet.

How Requests Move

Four data paths, one for each kind of demo.

HTTP Request

  1. The browser calls the API through TanStack Query, and the Developer Console records the request.
  2. Rate limiting and a request id apply to every request.
  3. ZodValidationPipe checks the parameters against the schema from packages/contracts; errors come back in one envelope.
  4. The module’s service builds the answer from the dataset or PostgreSQL.
  5. The browser parses the response with the same schema before it renders anything.

Real-time Stream

  1. The API holds one connection per exchange and keeps a two-hour price history.
  2. The generator creates orders at the rate the visitor chose (3–1,500 per second), in batches.
  3. The gateway on /api/v1/realtime checks the origin, caps connections and drops clients that fall behind.
  4. The browser validates every frame with Zod and keeps a bounded history.
  5. uPlot charts redraw in requestAnimationFrame without re-rendering React.

Workflow Run

  1. POST /workflows creates the run and a pending step for every node in one SQL statement.
  2. The executor starts every node whose inputs are ready, in parallel.
  3. Every change is one SQL statement with a version check, so no node runs twice.
  4. A failing node retries on its own up to maxAttempts; a manual retry resumes from the failed nodes.
  5. The browser polls the run twice a second and draws it with React Flow.
  1. The browser asks for a recorded scenario.
  2. The API runs the input guard, normalisation and prompt assembly live; the model steps are replayed from the recording.
  3. Zod validates the model’s output, and guardrails convert currencies and bound the dates.
  4. The filters become parameterised SQL and run over the in-memory dataset.

API Modules

Routes exactly as the controllers declare them, all under /api/v1.

API modules, their endpoints and where their state lives
ModuleEndpointsState
health

Readiness of the API and the database.

  • GET/health
Stateless
analytics

KPIs, time series, breakdowns and the orders table.

  • GET/analytics/summary
  • GET/analytics/timeseries
  • GET/analytics/breakdown
  • GET/analytics/records
In process memory
realtime

Event and price stream, two-hour price history.

  • WS/realtime
  • GET/realtime/prices
In process memory
workflows

Start, state, events and retry of runs.

  • POST/workflows
  • GET/workflows/:id
  • GET/workflows/:id/events
  • POST/workflows/:id/retry
PostgreSQL
ai-search

Recorded scenarios and live pipeline steps.

  • GET/ai-search/scenarios
  • GET/ai-search/scenarios/:id
In process memory

Key Decisions

Six decisions from the docs/decisions.md log, with the reason and the price we pay for each.

D3

One Contract, Written in Zod

Schemas in packages/contracts validate requests on the server and responses in the browser, and the types are inferred from them.

Trade-off: a contract change means rebuilding the package for both apps.

D5

A Modular Monolith on NestJS

One deployable, one module per demo: clear boundaries without network hops between them.

Trade-off: modules scale together; extracting a service later needs an explicit interface.

D13

Native WebSocket, Not Socket.IO

The browser’s WebSocket needs no client library, and the reconnect policy lives in one hook.

Trade-off: no built-in rooms or fallback transports; the stream is one-way.

D14

Workflows In-Process, State in PostgreSQL

A reload or a restart loses no run, and versioned updates stop a node from running twice.

Trade-off: execution stays in one process; distributed workers would need a queue.

D17

Canvas Charts with uPlot

SVG through React would re-render for every point; a canvas holds the frame rate at 1,500 events per second.

Trade-off: a canvas is invisible to screen readers, so the charts carry hidden tables or descriptions.

D18

AI Search Replays Recorded Traces

Multilingual behaviour is shown honestly without calling a model, while the code steps run live.

Trade-off: visitors cannot type their own request.

What the Platform Guarantees

What the tests check, not only what the documentation promises.

  • Contracts at Both Ends

    Every endpoint validates its input with the shared Zod schema, and the browser validates the response.

  • Rate Limits

    A global limit, a tighter one for workflow writes, and a cap on WebSocket connections.

  • One Error Envelope

    An error code and a request id instead of internal exception text.

  • CORS and Origin Checks

    Allowed origins come from configuration, for HTTP and WebSocket alike.

  • A Console Without Secrets

    The Developer Console only observes, redacts credentials and personal data, and bounds what it keeps.

  • Tests at Every Level

    Vitest for logic, API end-to-end tests against a real database, Playwright on desktop and mobile.

Shall We Discuss Your Product’s Architecture?

We will show how these decisions map onto your requirements: load, data, integrations and team.