Skip to content

Architecture

AgentReady Stack is a Turborepo monorepo designed for AI-assisted development. Three applications, two shared packages, and explicit patterns that AI agents understand.

Project Structure

agentready-stack/
├── apps/
│   ├── api/              # NestJS backend
│   ├── web/              # React Router v7 frontend
│   ├── cli/              # Oclif command-line interface
│   └── docs/             # VitePress documentation site
├── packages/
│   ├── database/         # MikroORM entities (shared)
│   └── api-client/       # Generated API client & types
├── docker/
│   ├── docker-compose.dev.yml
│   ├── Dockerfile.api
│   └── Dockerfile.web
├── turbo.json            # Build orchestration
└── pnpm-workspace.yaml   # Workspace definition

The Type Flow

This is the key architectural pattern that makes AgentReady Stack work with AI agents:

┌─────────────────────────────────────────────────────────────────┐
│                        BACKEND (NestJS)                         │
│                                                                 │
│   DTOs with @ApiProperty() decorators                           │
│   └── CreateUserDto, UserResponseDto, etc.                      │
└──────────────────────────────┬──────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                      OpenAPI Specification                      │
│                                                                 │
│   Auto-generated from NestJS decorators                         │
│   └── GET /api-json                                             │
└──────────────────────────────┬──────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                    Orval Code Generation                        │
│                                                                 │
│   Generates TypeScript client + types                           │
│   └── pnpm codegen                                              │
└──────────────────────────────┬──────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                     @platform/api-client                        │
│                                                                 │
│   Type-safe fetch functions + interfaces                        │
│   └── import { createUser, UserResponseDto } from '...'         │
└──────────────────────────────┬──────────────────────────────────┘

               ┌───────────────┼───────────────┐
               ▼               ▼               ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│  Frontend (Web)  │ │   CLI (Oclif)    │ │  + More Apps     │
│                  │ │                  │ │                  │
│  Type-safe API   │ │  Type-safe API   │ │  Mobile, Admin,  │
│  calls           │ │  calls           │ │  Partner Portal  │
└──────────────────┘ └──────────────────┘ └──────────────────┘

Why this matters: Change a field in a backend DTO, run pnpm codegen, and TypeScript will show you every place in the frontend and CLI that needs updating.

Backend (NestJS)

Architecture Pattern

The API follows a 3-layer architecture:

Controller → Service → Database
  • Controllers handle HTTP, validation, and response transformation
  • Services contain business logic
  • Repositories are injected directly (no custom repository classes)

Module Structure

apps/api/src/
├── auth/                 # Authentication strategies & guards
│   ├── strategies/       # JWT, Local, API Key
│   ├── guards/           # JwtAuthGuard, LocalAuthGuard
│   └── decorators/       # @GetUser(), @Public()
├── users/                # User management
├── organizations/        # Multi-tenancy
├── api-keys/             # CLI authentication
├── mail/                 # Email service + templates
└── shared/               # Filters, constants, utilities

Authentication Strategies

StrategyUse CaseHow It Works
JWTWeb app sessionsHTTP-only cookies with access + refresh tokens
LocalLogin endpointEmail/password validation
API KeyCLI & externalBearer token in header

Response Transformation

Controllers never return raw entities. They transform to DTOs:

typescript
@Get(':id')
async findOne(@Param('id') id: string): Promise<UserResponseDto> {
  const user = await this.usersService.findOne(id);
  return {
    id: user.id,
    email: user.email,
    createdAt: user.createdAt.toISOString(),
  };
}

Frontend (React Router v7)

Data Loading Pattern

Always use loaders, never useEffect:

typescript
// ✅ Correct
export async function loader({ request }: Route.LoaderArgs) {
  const session = await requireUser(request);
  return { user: await getUser(session.accessToken) };
}

// ❌ Wrong
function Component() {
  useEffect(() => {
    fetchUser();
  }, []);
}

Route Structure

apps/web/app/
├── routes/
│   ├── _index.tsx        # Home page
│   ├── login.tsx         # Login form
│   ├── register.tsx      # Registration
│   ├── dashboard.tsx     # Authenticated dashboard
│   └── orgs.$id.tsx      # Organization detail (dynamic)
├── components/           # Reusable components
├── services/             # API client wrapper
└── utils/                # Helpers

Form Handling

Forms use React Router's native <Form> component with server-side validation:

typescript
export async function action({ request }: Route.ActionArgs) {
  const formData = await request.formData();
  const name = formData.get('name') as string;

  if (!name) return { error: 'Name is required' };

  await createOrg(name);
  return redirect('/dashboard');
}

CLI (Oclif)

Authentication Flow

The CLI uses a browser-based OAuth flow:

  1. User runs platform auth login
  2. CLI opens browser to /cli/auth
  3. User logs in on web app
  4. Web app generates auth code
  5. CLI exchanges code for API key
  6. API key stored in ~/.config/platform/

Command Structure

apps/cli/src/
├── commands/
│   ├── auth/
│   │   ├── login.ts      # Browser-based login
│   │   ├── logout.ts     # Clear credentials
│   │   └── status.ts     # Show current user
│   ├── orgs/
│   │   └── list.ts       # List organizations
│   └── api-keys/
│       └── list.ts       # List API keys
├── lib/
│   └── api-client.ts     # Authenticated API wrapper
└── config/
    └── credentials.ts    # Credential storage

Protected Commands

Commands requiring auth extend AuthenticatedCommand:

typescript
export default class ListOrgs extends AuthenticatedCommand {
  async run() {
    const orgs = await this.apiClient.getOrganizations();
    this.table(orgs, { name: {}, slug: {} });
  }
}

Shared Packages

@platform/database

Contains MikroORM entities shared between apps:

typescript
// packages/database/src/entities/user.entity.ts
@Entity()
export class UserEntity extends BaseEntity {
  @Property()
  email!: string;

  @Property()
  emailVerified: boolean = false;

  @OneToMany(() => OrgMembershipEntity, (m) => m.user)
  memberships = new Collection<OrgMembershipEntity>(this);
}

@platform/api-client

Auto-generated from OpenAPI spec. Never edit manually.

typescript
// Usage in frontend
import { getOrganizations, OrganizationDto } from '@platform/api-client';

const orgs: OrganizationDto[] = await getOrganizations();

Testing Strategy

AppFrameworkPattern
APIJestMock repositories, test services
API E2EJest + mongodb-memory-serverFull request/response testing
WebVitest + Testing LibraryMock loaders, test components
CLIJestMock credentials and API client

All tests run without Docker—API E2E tests use mongodb-memory-server for an in-memory database, while unit tests use mock repositories.

Why AI Agents Navigate This Easily

The architecture has explicit patterns that AI agents recognize:

  • Module boundaries are clear — Each NestJS module is self-contained
  • Type flow is explicit — DTOs define the contract, codegen enforces it
  • Test patterns are consistent — AI can replicate existing test structures
  • File organization is predictable — AI knows where new code belongs
  • Adding apps is straightforward — New frontends (mobile, admin, partner portals) import @platform/api-client and immediately get type-safe API access

Next Steps