Introduction

Architecture

Understanding the high-level design and data flow of Nuda Kit.

Nuda Kit uses a decoupled architecture. The frontend and backend are completely separate applications that can be developed, tested, and deployed independently. They communicate strictly via HTTP requests (REST API).

High-Level Overview

┌─────────────────────────────────────────────────────────────────────────┐
│                              Client (Browser)                           │
└─────────────────────────────────┬───────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                         Frontend (Nuxt 4)                               │
│                         localhost:3000                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐                │
│  │  Pages   │  │Components│  │  Stores  │  │ Services │                │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘                │
└─────────────────────────────────┬───────────────────────────────────────┘
                                  │ REST API
                                  ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                        Backend (AdonisJS v6)                            │
│                         localhost:3333                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐                │
│  │  Routes  │  │Controllers│  │ Services │  │  Models  │                │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘                │
└───────┬─────────────────────────┬───────────────────────┬───────────────┘
        │                         │                       │
        ▼                         ▼                       ▼
┌───────────────┐       ┌─────────────────┐       ┌───────────────┐
│  PostgreSQL   │       │      Redis      │       │    Stripe     │
│   Database    │       │     Queues      │       │   Payments    │
└───────────────┘       └─────────────────┘       └───────────────┘

The Four Layers

Client (Browser)

The user interacts with the Vue.js application running in the browser. All UI rendering happens here after the initial page load.

Frontend (Nuxt)

Handles SSR/SSG for SEO, serves static assets, manages client-side routing, and orchestrates API calls to the backend.

Backend (AdonisJS)

The "brain" of the operation. Exposes a JSON REST API, handles business logic, authentication, authorization, and database operations.

Infrastructure

Docker containers running PostgreSQL (data), Redis (queues), MailHog (email testing), and Stripe CLI (webhook forwarding).

Data Flow Example

When a user performs an action like "Create Team", here's what happens:

User Action

User clicks "Create Team" button in the Nuxt UI.

API Request

The frontend sends a POST /teams request to the AdonisJS server using TanStack Query.

Validation

AdonisJS validates the request payload using VineJS. Invalid requests return a 422 error immediately.

Authorization

Bouncer checks if the user has permission to create a team (e.g., subscription limits).

Business Logic

The controller creates the team record in PostgreSQL and associates the user as the owner.

Response

AdonisJS returns the created team as JSON with a 201 status code.

UI Update

TanStack Query receives the response, invalidates the teams cache, and the UI updates automatically.

The Dual-Server Setup

In development, you run two separate servers:

ServicePortURLDescription
Nuxt3000http://localhost:3000Frontend server - visit this in your browser
AdonisJS3333http://localhost:3333API server - Nuxt communicates with this
The frontend's NUXT_PUBLIC_API_BASE_URL environment variable points to the backend server URL.

Project Structure

The codebase is organized into three main directories:

nuda-kit/
├── frontend/                 # Nuxt 4 Application
├── backend/                  # AdonisJS v6 Application
└── etc/
    └── docker/               # Docker Compose & configs

Frontend Structure

frontend/
├── app/
│   ├── assets/               # CSS, images, brand logos
│   ├── components/           # Vue components (shadcn-vue + custom)
│   │   ├── app/              # Application-specific components
│   │   ├── landing/          # Landing page components
│   │   └── ui/               # shadcn-vue primitives
│   ├── composables/          # Vue composables
│   │   └── queries/          # TanStack Query hooks
│   ├── layouts/              # Page layouts (app, auth, settings)
│   ├── middleware/           # Route middleware (auth, guest)
│   ├── pages/                # File-based routing
│   │   ├── app/              # Authenticated app pages
│   │   │   ├── auth/         # Login, signup, forgot password
│   │   │   └── settings/     # User & team settings
│   │   └── index.vue         # Landing page
│   ├── plugins/              # Nuxt plugins
│   ├── services/api/         # API service functions
│   ├── stores/               # Pinia stores
│   └── types/                # TypeScript type definitions
├── public/                   # Static assets
└── nuxt.config.ts            # Nuxt configuration

Backend Structure

backend/
├── app/
│   ├── abilities/            # Bouncer ability definitions
│   ├── controllers/          # HTTP controllers (by domain)
│   │   ├── ai/               # AI streaming endpoints
│   │   ├── billing/          # Stripe checkout & portal
│   │   ├── invitations/      # Team invitation handling
│   │   ├── plans/            # Subscription plans
│   │   ├── teams/            # Team CRUD & members
│   │   ├── users/            # Auth & user management
│   │   └── webhooks/         # Stripe webhooks
│   ├── events/               # Event dispatchers
│   ├── exceptions/           # Custom exception handlers
│   ├── jobs/                 # Background jobs (BullMQ)
│   ├── listeners/            # Event listeners
│   ├── mails/                # Email notifications
│   ├── middleware/           # HTTP middleware
│   ├── models/               # Lucid ORM models
│   ├── policies/             # Bouncer authorization policies
│   ├── services/             # Business logic services
│   └── validators/           # VineJS validation schemas
├── config/                   # Configuration files
├── database/
│   ├── migrations/           # Database migrations
│   └── seeders/              # Database seeders
├── resources/views/emails/   # Edge email templates
├── start/
│   ├── routes/               # Route definitions (by domain)
│   ├── events.ts             # Event bindings
│   ├── kernel.ts             # Middleware stack
│   └── limiter.ts            # Rate limiting rules
├── tests/                    # Japa test suites
└── adonisrc.ts               # AdonisJS configuration

API Domains

The backend API is organized into logical domains:

DomainPrefixDescription
Users/usersAuthentication, profile, avatar
Teams/teamsTeam CRUD, members, invitations
Invitations/invitationsAccept/reject invitations
Billing/billingStripe checkout, portal, invoices
Plans/plansSubscription plan listing
AI/aiAI chat streaming
Webhooks/webhooksStripe webhook handler
Docs/docsSwagger API documentation

Key Architectural Decisions

Decoupled Frontend/Backend
REST API
Enables independent scaling, separate deployments, and the ability to build mobile apps or third-party integrations using the same API.
Controller-per-Action
Single Responsibility
Each controller handles exactly one action (e.g., CreateTeamController), making code easy to find, test, and maintain.
Domain-based Organization
Modular Structure
Routes, controllers, and tests are organized by domain (users, teams, billing) rather than by type, keeping related code together.
Event-Driven Side Effects
Loose Coupling
Actions like sending emails are handled via events and background jobs, keeping controllers focused on the main request/response cycle.
TanStack Query for Data
Server State Management
API data is managed by TanStack Query with caching and automatic invalidation, while Pinia handles only client-side UI state.