Skip to main content
Sonamu is a TypeScript framework that enables entity-centric full-stack development. This guide explains Sonamu’s overall architecture and core operating principles.

Sonamu’s Philosophy

Sonamu is designed based on the following principles:
1

1. Entity First

All development starts with entity definition. Define the data structure first, and the rest is automatically generated.
2

2. Type Safety

Complete type safety is guaranteed from backend to frontend. Errors can be caught at compile time.
3

3. Code Generation

Repetitive boilerplate code is automatically generated. Developers can focus only on business logic.
4

4. Developer Experience

Excellent developer experience with HMR, type inference, Sonamu UI, and more.

Overall Architecture

Sonamu operates with 6 layers automatically connected:

Core Components

1. Entity

Entity is the starting point of everything in Sonamu.
user.entity.json
{
  "entityId": "User",
  "tableName": "users",
  "title": "User",
  "properties": [
    {
      "name": "id",
      "type": "int",
      "isPrimary": true
    },
    {
      "name": "email",
      "type": "varchar"
    },
    {
      "name": "name",
      "type": "varchar"
    }
  ],
  "subsets": {
    "A": ["id", "email", "name", "created_at"],
    "C": ["id", "email", "name"]
  }
}
Role of Entity Definition
  • Source of database schema
  • Basis for TypeScript type generation
  • Definition of API interfaces
  • Standard for frontend types

2. Type System

3 type files are automatically generated from entity definitions:
// Auto-generated + extensible
import { z } from "zod";
import { UserBaseSchema } from "../sonamu.generated";

// Base type (auto-generated)
export type User = z.infer<typeof UserBaseSchema>;

// Custom types (can be written directly)
export const UserSaveParams = UserBaseSchema.partial({
  id: true,
  created_at: true,
});
export type UserSaveParams = z.infer<typeof UserSaveParams>;
Three Type Files
  1. {entity}.types.ts - Per-entity types (extensible)
  2. sonamu.generated.ts - All Base schemas (auto-generated)
  3. sonamu.generated.sso.ts - Subset queries (auto-generated)

3. Model (Business Logic)

Model handles the business logic of entities.
user.model.ts
import { api, BaseModelClass } from "sonamu";
import type { UserSubsetKey, UserSubsetMapping } from "../sonamu.generated";
import { userSubsetQueries } from "../sonamu.generated.sso";

class UserModelClass extends BaseModelClass<
  UserSubsetKey,
  UserSubsetMapping,
  typeof userSubsetQueries
> {
  constructor() {
    super("User", userSubsetQueries);
  }

  // CRUD API
  @api({ httpMethod: "GET", clients: ["axios", "tanstack-query"] })
  async findById<T extends UserSubsetKey>(
    subset: T,
    id: number,
  ): Promise<UserSubsetMapping[T]> {
    const user = await this.db().where("id", id).first();
    return user;
  }

  // Custom business logic
  @api({ httpMethod: "POST", clients: ["axios"] })
  async changePassword(userId: number, newPassword: string): Promise<void> {
    await this.db()
      .where("id", userId)
      .update({ password: await this.hashPassword(newPassword) });
  }

  private async hashPassword(password: string): Promise<string> {
    // Password hashing logic
    return password;
  }
}

export const UserModel = new UserModelClass();
Model Features
  • Auto REST API generation with @api decorator
  • Basic CRUD methods provided by inheriting BaseModelClass
  • Type-safe DB queries with Puri query builder
  • Focus only on business logic

4. Syncer (Synchronization System)

Syncer is Sonamu’s core engine. It detects file changes and automatically generates code.
// Main roles of Syncer

1. File change detection (checksum-based)
   - entity.json change β†’ Types regeneration
   - model.ts change β†’ Service regeneration
   - types.ts change β†’ Copy to Web

2. Auto code generation
   - sonamu.generated.ts
   - sonamu.generated.sso.ts
   - {Entity}Service.ts (frontend)

3. File synchronization
   - api/src/application β†’ web/src/services
   - Type file copying
   - sonamu.shared.ts deployment

4. HMR trigger
   - Invalidate changed modules
   - Auto restart API server
When Syncer Runs
  • Auto: During file changes while pnpm dev is running (HMR)
  • Manual: When pnpm sync command is executed

5. API Layer (REST API)

The Model’s @api decorator automatically generates REST APIs.
// Model method
@api({ httpMethod: "GET", clients: ["axios"] })
async findById(subset: UserSubsetKey, id: number): Promise<User> {
  // ...
}

// ↓ Auto conversion

// REST API endpoint
GET /api/users/:id?subset=A

// Request
curl http://localhost:1028/api/users/1?subset=A

// Response
{
  "id": 1,
  "email": "user@example.com",
  "name": "John Doe",
  "created_at": "2025-01-06T12:00:00Z"
}
What’s Auto-Generated
  • βœ… REST API routes
  • βœ… Request parameter validation (Zod)
  • βœ… Response types
  • βœ… Error handling
  • βœ… API documentation (sonamu.generated.http)

6. Frontend Service (Frontend Integration)

Model APIs are automatically generated as frontend Services.
web/src/services/UserService.ts (auto-generated)
export class UserService {
  static async findById(subset: UserSubsetKey, id: number): Promise<User> {
    const res = await axios.get(`/api/users/${id}`, {
      params: { subset },
    });
    return res.data;
  }

  static async changePassword(
    userId: number,
    newPassword: string,
  ): Promise<void> {
    await axios.post("/api/users/changePassword", {
      userId,
      newPassword,
    });
  }
}
Type Safety Backend types are synchronized directly to the frontend, so type mismatch errors can be caught at compile time!

Development Flow

Let’s see how Sonamu works during actual development:
1

1. Entity Definition (Sonamu UI)

{
  "entityId": "Post",
  "properties": [
    { "name": "id", "type": "int", "isPrimary": true },
    { "name": "title", "type": "varchar" }
  ]
}
Auto-generated on save:
  • post.types.ts
  • sonamu.generated.ts updated
2

2. Migration (Migration tab)

CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  title VARCHAR(255) NOT NULL
);
On execution:
  • Table created in database
3

3. Write Model (manual or scaffolding)

@api({ httpMethod: "GET" })
async findById(id: number): Promise<Post> {
  return await this.db().where("id", id).first();
}
Auto-generated on save:
  • REST API: GET /api/posts/:id
  • PostService.ts (frontend)
  • sonamu.generated.http updated
4

4. Frontend Usage

// Type-safe API call
const post = await PostService.findById(1);
console.log(post.title); // βœ… Type checked
console.log(post.invalid); // ❌ Compile error!
Type safety:
  • Frontend immediately detects errors when backend types change

HMR (Hot Module Replacement)

Sonamu provides a powerful HMR system.

How HMR Works

// 1. File change detection
user.model.ts modified β†’ Watcher detects

// 2. Module invalidation
hmr-hook analyzes dependency graph
user.model.ts and dependent modules invalidated

// 3. Regeneration (Syncer)
- UserService.ts regenerated
- API routes re-registered

// 4. Server restart
graceful shutdown β†’ reload
Benefits of HMR
  • βœ… Fast feedback - Reflected within 1-2 seconds after code change
  • βœ… State preservation - Database connections, etc. maintained
  • βœ… Auto sync - Frontend Service auto-updated

Auto Generation Mechanism

Let’s summarize what Sonamu automatically generates:

On Entity Definition Change

user.entity.json modified
↓
Auto-generated:
βœ… user.types.ts (type definitions)
βœ… sonamu.generated.ts (Base schemas)
βœ… sonamu.generated.sso.ts (Subset queries)
βœ… migration SQL (table changes)

On Model File Change

user.model.ts modified
↓
Auto-generated:
βœ… UserService.ts (frontend)
βœ… services.generated.ts (Service integration)
βœ… sonamu.generated.http (API documentation)
βœ… REST API routes re-registered

On Types File Change

user.types.ts modified
↓
Auto-synced:
βœ… web/src/services/user.types.ts (copied)
βœ… Immediately usable in frontend
Files You Should Never Modify
  • sonamu.generated.ts - Overwritten on next sync
  • {Entity}Service.ts - Overwritten on next sync
  • sonamu.generated.sso.ts - Overwritten on next sync
These files are always auto-generated, so don’t modify them directly!

Type Safety Flow

Visualizing Sonamu’s End-to-End type safety:
// 1. Entity definition
{
  "name": "email",
  "type": "varchar"
}

↓

// 2. Type generation (auto)
type User = {
  email: string;
}

↓

// 3. Model method (type-safe)
async findByEmail(email: string): Promise<User> {
  return await this.db().where("email", email).first();
}

↓

// 4. REST API (auto-generated, type-safe)
GET /api/users/email/:email
Response: User

↓

// 5. Frontend Service (auto-generated, type-safe)
static async findByEmail(email: string): Promise<User> {
  const res = await axios.get(`/api/users/email/${email}`);
  return res.data; // type: User
}

↓

// 6. React Component (type-safe)
const user = await UserService.findByEmail("test@example.com");
console.log(user.email); // βœ… Type checked
console.log(user.invalid); // ❌ Compile error!
Type Safety Guaranteed
  1. Entity definition β†’ Type generation
  2. Model β†’ API type inference
  3. API β†’ Service type synchronization
  4. Service β†’ UI type checking
When types change at any stage, it’s reflected across the entire chain and errors are caught at compile time!

Key Advantages of Sonamu

1. Development Speed Improvement

Traditional approach:
1. Design database table
2. Write migration
3. Define backend types
4. Write API controller
5. Register API routes
6. Define frontend types
7. Write API client
⏱️ Total time: 2-3 hours

Sonamu approach:
1. Define entity (Sonamu UI)
2. Scaffolding (auto Model generation)
⏱️ Total time: 10-15 minutes

βœ… About 90% time reduction!

2. Type Safety

// ❌ Traditional approach: runtime error
const user = await fetch("/api/users/1").then(r => r.json());
console.log(user.eamil); // Typo! Only discovered at runtime ⚠️

// βœ… Sonamu: compile error
const user = await UserService.findById("A", 1);
console.log(user.eamil); // Compile error! Discovered immediately βœ…

3. Maintainability

// Entity change: email β†’ username
// ❌ Traditional approach:
// 1. Write DB Migration
// 2. Modify backend types
// 3. Modify API
// 4. Modify frontend types
// 5. Modify all API call sites
// ⚠️ Missed spots cause runtime errors!

// βœ… Sonamu:
// 1. Change field name in entity.json
// 2. Migration auto-generated
// 3. All types auto-updated
// 4. Compile errors immediately show where changes are needed
// βœ… Safe changes without missing anything!

4. Consistency

// βœ… Sonamu automatically maintains consistent code style

// All Services follow the same pattern
UserService.findById(...)
PostService.findById(...)
CommentService.findById(...)

// All types have the same structure
type User = z.infer<typeof UserBaseSchema>;
type Post = z.infer<typeof PostBaseSchema>;
type Comment = z.infer<typeof CommentBaseSchema>;

Constraints and Trade-offs

Sonamu’s powerful features come with some constraints:
Constraints to Be Aware Of
  1. Learning curve: Need to understand Sonamu’s concepts and rules
  2. Cannot modify auto-generated files: Manual modifications get overwritten
  3. Entity-centric design required: Must follow the Sonamu way
  4. Complex queries: Very complex cases may require Raw SQL
But the advantages far outweighβœ… Development speed improvement (90% time reduction) βœ… Type safety guaranteed βœ… Improved maintainability βœ… Code consistency βœ… Team productivity improvement

Next Steps

Now that you understand how Sonamu works, learn about each component in detail: