Skip to main content

Blueprint System Overview

Radish CLI uses a comprehensive blueprint system to define your application. Blueprints are JSON files that describe your app's structure, behavior, and data model, enabling complete code generation from declarative specifications.

What Are Blueprints?

Blueprints are declarative configuration files that describe:

  • What your application should do
  • How it should behave
  • Who can access what

The CLI reads these blueprints and generates production-ready TypeScript code, including:

  • Type-safe contracts (Zod schemas + TypeScript types)
  • Database models with automatic indexing
  • Business logic services with authorization
  • REST API endpoints
  • Authentication and permission systems

Blueprint Types

Radish supports three blueprint types:

📋 App Blueprint ({app}.app.json)

The master document for your application — defines high-level metadata, audience, workflows, features, and entity overview. Other blueprints (types, roles) derive their content from it.

{
"version": 1,
"app": {
"name": "Library",
"displayName": "Library Management System",
"description": "A library system for managing books, authors, members, and loans",
"version": "0.1.0",
"domain": "education",
"tags": ["library", "books", "lending"]
},
"audience": {
"primary": "Library patrons searching and borrowing books",
"secondary": "Librarians managing catalog and loans",
"admin": "System administrators managing users and settings"
},
"workflows": [
{
"name": "Borrow Book",
"actor": "patron",
"description": "Patron searches catalog, selects book, requests loan, librarian approves"
}
],
"features": {
"auth": true,
"roles": true,
"adminPanel": true,
"api": true,
"search": true,
"pagination": true
},
"entityOverview": {
"catalog": [
{ "Book": "Books in the library catalog. Owned by system." },
{ "Author": "Book authors with biographical info." }
],
"lending": [
{ "Loan": "Active book loans tracked by member and due date." }
]
},
"accessPatterns": {
"public": ["Browse catalog", "Search books"],
"authenticated": ["Borrow books", "View own loans"],
"admin": ["Manage catalog", "Manage members", "Override loans"]
},
"database": {
"type": "mongodb",
"name": "library"
}
}

Sections:

  • app — Name, description, version, domain, and tags
  • audience — Primary, secondary, and admin user personas
  • workflows — Key user journeys with actors and steps
  • categories — Content categorization (optional)
  • style — Branding hints: theme, tone, palette, typography
  • features — Boolean flags for auth, roles, search, pagination, etc.
  • entityOverview — High-level entity groupings by domain concern (not field-level detail)
  • accessPatterns — Who can do what at each access level
  • database — Database engine and name

📖 Complete App Blueprint Documentation →

🗄️ Types Blueprint ({app}.types.json)

Defines your data model - entities, fields, relationships, and business rules.

{
"version": 1,
"defaults": {
"timestamps": true,
"owned": true
},
"entities": {
"Book": {
"label": "Book",
"plural": "books",
"fields": {
"title": { "type": "string", "required": true, "search": true },
"isbn": { "type": "string", "unique": true, "optional": true },
"authorId": { "type": "objectId", "ref": "Author", "required": true },
"status": { "type": "enum", "values": ["draft", "published"], "default": "draft" }
},
"filters": ["authorId", "status"],
"indexes": [
{ "fields": ["authorId", "status"] }
]
}
}
}

Generates:

  • TypeScript contracts with Schema suffix naming (bookSchema, createBookSchema)
  • Database models with automatic indexes
  • Repository classes with CRUD operations
  • Service classes with business logic and authorization
  • REST API endpoints with populate support
  • Permission registry

📖 Complete Types Blueprint Documentation →

🛡️ Roles Blueprint ({app}.roles.json)

Defines roles and permissions for authentication and authorization.

{
"version": 1,
"roles": {
"LIBRARIAN": {
"label": "Librarian",
"description": "Can manage all books and authors",
"isSystem": false,
"permissions": [
"book:view:all",
"book:edit:all",
"author:view:all",
"author:edit:all"
]
}
}
}

Generates:

  • Role entities and services
  • Permission registry with metadata
  • Authentication middleware
  • Authorization guards and helpers
  • API key management system
  • User management endpoints

📖 Complete Roles Blueprint Documentation →

File Organization

my-app/
├── blueprints/
│ ├── my-app.app.json # Application metadata & overview
│ ├── my-app.types.json # Data model
│ └── my-app.roles.json # Authentication & permissions
└── .radish/
└── lib/
└── datalayer/
├── contracts/ # Generated Zod schemas & types
│ ├── common.ts
│ ├── book.ts
│ ├── author.ts
│ └── metadata/
├── core/ # Core infrastructure
│ ├── auth/ # Authentication & guards
│ ├── http/ # HTTP adapters & handlers
│ ├── models/ # Mongoose models
│ └── repos/ # Repository classes
├── routes/ # REST API endpoints
│ └── api/
├── services/ # Business logic services
└── docs/ # Generated API docs

Blueprint Philosophy

Declarative Over Imperative

Describe what you want, not how to build it. The generator handles implementation details.

Convention Over Configuration

Smart defaults reduce boilerplate. Override only when needed.

Type Safety First

All generated code is fully typed with TypeScript. Runtime validation with Zod schemas.

Schema Suffix Naming

Clear distinction between runtime validators and compile-time types:

  • Schemas: bookSchema, createBookSchema (camelCase + Schema)
  • Types: Book, CreateBook (PascalCase)

Progressive Enhancement

Start minimal and add features as needed. Every feature is opt-in.

Version Control Friendly

Human-readable JSON that diffs well. Generated code is organized and predictable.

Working with Blueprints

Creating Your First Blueprint

1. Create the app blueprint:

mkdir -p blueprints
touch blueprints/library.app.json
{
"version": 1,
"app": {
"name": "Library",
"description": "A library management system for books and authors",
"domain": "education"
},
"features": {
"auth": true,
"roles": true,
"api": true,
"search": true
}
}

2. Create the types blueprint:

touch blueprints/library.types.json
{
"version": 1,
"defaults": {
"timestamps": true,
"owned": true
},
"entities": {
"Book": {
"plural": "books",
"fields": {
"title": { "type": "string", "required": true, "search": true },
"isbn": { "type": "string", "unique": true },
"authorId": { "type": "objectId", "ref": "Author", "required": true }
}
}
}
}

3. Create the roles blueprint:

touch blueprints/library.roles.json
{
"version": 1,
"roles": {
"LIBRARIAN": {
"label": "Librarian",
"permissions": [
"book:view:all",
"book:edit:all"
]
}
}
}

4. Generate the application:

radish-cli create datalayer . --schema blueprints/library.types.json

This generates all code in .radish/lib/datalayer/.

Regenerating After Changes

Whenever you modify a blueprint, regenerate:

radish-cli create datalayer . --schema blueprints/library.types.json

The generator overwrites all files in .radish/lib/datalayer/ with fresh code based on your updated blueprints.

Blueprint Validation

Each blueprint type has JSON schema validation that runs automatically during generation:

❌ Blueprint validation failed:
[
{
"instancePath": "/entities/Book/fields/invalidField",
"message": "must have required property 'type'"
}
]

Common validation errors:

  • Missing required properties (type, plural, etc.)
  • Invalid field types
  • Circular entity references
  • Invalid permission patterns

Cross-Blueprint References

Blueprints work together to create cohesive applications:

In the types blueprint, define the entity:

{
"Book": {
"fields": {
"title": { "type": "string", "required": true }
}
}
}

In the roles blueprint, reference the entity for permissions:

{
"LIBRARIAN": {
"permissions": [
"book:view:all",
"book:edit:all"
]
}
}

Generated Code Structure

Contracts Package

Located in .radish/lib/datalayer/contracts/:

// Import schemas and types
import { bookSchema, createBookSchema } from '@generated/datalayer/contracts';
import type { Book, CreateBook } from '@generated/datalayer/contracts';

// Runtime validation
const book = bookSchema.parse(data);

// Type annotations
function processBook(book: Book): void { ... }

📖 Complete Contracts Documentation →

Server Package

Located in .radish/lib/datalayer/:

// Import services
import { BookService } from '@generated/datalayer/services';
import type { AuthContext } from '@generated/datalayer/core/auth';

// Use in your application
const ctx: AuthContext = { userId, roles, permissions };
const service = new BookService(ctx);
const books = await service.list({ limit: 20 });

📖 Complete Services Documentation →

Common Patterns

Entity Relationships

{
"Book": {
"fields": {
"authorId": { "type": "objectId", "ref": "Author", "required": true },
"categoryId": { "type": "objectId", "ref": "Category" },
"reviewerIds": { "type": "array", "items": { "type": "objectId", "ref": "User" } }
}
}
}

Generated features:

  • Automatic indexing on reference fields
  • Populate support via ?populate=authorId,categoryId
  • Type-safe references in TypeScript

Search and Filtering

{
"Book": {
"fields": {
"title": { "type": "string", "search": true },
"author": { "type": "string", "search": true },
"status": { "type": "enum", "values": ["draft", "published"] }
},
"filters": ["status", "categoryId"]
}
}

Generated features:

  • Full-text search on search: true fields via ?q=search+term
  • Filterable fields via query parameters: ?status=published
  • Automatic indexes on filter fields

Timestamps and Ownership

{
"defaults": {
"timestamps": true,
"owned": true
},
"Book": {
"fields": {
"title": { "type": "string", "required": true }
}
}
}

Versioning

{
"Book": {
"versioning": "full",
"fields": {
"title": { "type": "string", "required": true },
"price": {
"type": "float",
"trackChanges": true
}
}
}
}

Generated features:

  • Version history endpoints
  • Audit logs
  • Revert capability (full versioning only)
  • Field-level change tracking with approvals

📖 Complete Versioning Documentation →

Best Practices

1. Use Meaningful Names

{
"Book": {
"plural": "books",
"fields": {
"title": { "type": "string" },
"publishedAt": { "type": "isoDate" }
}
}
}

Avoid abbreviated or unclear names like Bk with fields like t or d.

2. Define Relationships Consistently

Use ref for type safety:

{
"Book": {
"fields": {
"authorId": { "type": "objectId", "ref": "Author" }
}
}
}

Avoid using plain strings for references (e.g., { "type": "string" }) as they lack validation.

3. Use Filters for Common Queries

{
"Book": {
"filters": ["status", "categoryId", "publishedAt"]
}
}

Without filters defined, queries will scan the entire collection.

4. Document with Labels and Descriptions

{
"Book": {
"label": "Book",
"description": "Books in the library catalog",
"fields": {
"isbn": {
"type": "string",
"label": "ISBN",
"description": "International Standard Book Number",
"helpText": "Format: XXX-X-XXX-XXXXX-X"
}
}
}
}

5. Version Your Blueprints

Always specify "version": 1 at the top of your blueprints. This enables future migration paths when blueprint schemas evolve.

Troubleshooting

Blueprint validation failed

Cause: Invalid JSON syntax or schema violations.

Solution:

  • Check JSON syntax (braces, commas, quotes)
  • Verify all required properties are present
  • Check error message for specific field causing issue

Cannot find referenced entity

Cause: Entity referenced in ref doesn't exist.

Solution:

  • Ensure referenced entity is defined in same blueprint
  • Check spelling of entity name (case-sensitive)
  • Define entities before referencing them

Generated code has TypeScript errors

Cause: Blueprint changes not fully regenerated.

Solution:

# Delete generated code
rm -rf .radish/lib/datalayer

# Regenerate fresh
radish-cli create datalayer . --schema blueprints/app.types.json

# Install dependencies
cd .radish/lib/datalayer && npm install

Migration & Versioning

Adding New Entities

Safe - just regenerate:

radish-cli create datalayer . --schema blueprints/app.types.json

Modifying Existing Entities

Safe changes (no migration needed):

  • Adding optional fields
  • Adding new entities
  • Changing labels/descriptions
  • Adding indexes

Breaking changes (migration required):

  • Adding required fields
  • Changing field types
  • Removing fields
  • Renaming fields

📖 Complete Migration Guide →

Next Steps