Appearance
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 definitionThe 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, utilitiesAuthentication Strategies
| Strategy | Use Case | How It Works |
|---|---|---|
| JWT | Web app sessions | HTTP-only cookies with access + refresh tokens |
| Local | Login endpoint | Email/password validation |
| API Key | CLI & external | Bearer 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/ # HelpersForm 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:
- User runs
platform auth login - CLI opens browser to
/cli/auth - User logs in on web app
- Web app generates auth code
- CLI exchanges code for API key
- 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 storageProtected 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
| App | Framework | Pattern |
|---|---|---|
| API | Jest | Mock repositories, test services |
| API E2E | Jest + mongodb-memory-server | Full request/response testing |
| Web | Vitest + Testing Library | Mock loaders, test components |
| CLI | Jest | Mock 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-clientand immediately get type-safe API access
Next Steps
- Learn the AI Workflow to start building with your AI agent
- Check the Quick Start to get running