Skip to main content
TypeScript has a powerful type system. However, many frameworks fail to fully leverage this strength. Sonamu is designed with TypeScript’s type system at its core.

Who Is This Framework For

Productivity-Focused Developers

Those who want to focus on business logic without wasting time on repetitive tasks

Teams That Trust Type Safety

Teams that prefer compile errors over runtime errors and maximize IDE assistance

Backend Developers Collaborating with Frontend

Those who want to escape the dual work of API spec documentation and client writing

Startups Needing Rapid Prototyping

Teams that want to build MVPs quickly while minimizing technical debt

Core Philosophy: Single Source of Truth

Define one Entity, and TypeScript handles the rest.
Define an Entity in one place, and the entire system stays synchronized. No need to manually align types, document API specs, or write frontend clients.

Backend and Frontend Type Synchronization

Typically, when building backend APIs and using them on the frontend, type synchronization is difficult. With Sonamu, backend changes are immediately reflected on the frontend, catching errors at compile time.
// Backend API
router.get('/api/users/:id', async (req, res) => {
  const user = await db.users.findById(req.params.id);
  res.json(user);
});

// Frontend client (manually written)
async function getUser(id: number): Promise<any> {  // ← any!
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

const user = await getUser(123);
console.log(user.username);  // Potential runtime error
Sonamu approach compile error

Fully Type-safe Query Builder: Puri

Sonamu provides Puri, a type-safe query builder based on Knex. Table names, column names, and relationships are all type-checked.
// Type-safe query
const users = await this.getPuri("r")
  .table("users")           // ✅ Table name autocomplete
  .select("id", "email")    // ✅ Column name type check
  .where("role", "admin")   // ✅ Column name + value type check
  .orderBy("created_at", "desc");

// Relation join
const posts = await this.getPuri("r")
  .table("posts")
  .join("users", "posts.author_id", "users.id")  // ✅ Relationship type check
  .select("posts.*", "users.username as author_name");
Key Features:
  • ✅ Types auto-extracted from Entity definition
  • ✅ Autocomplete for table names, column names, and relationships
  • ✅ Wrong column names cause compile errors
  • ✅ All Knex features supported

Puri Guide

Write type-safe queries

Frontend Integration

When you define a backend API, frontend Services and TanStack Query Hooks are automatically generated.
// Backend: Define API with @api decorator
@api({ httpMethod: "GET" })
async getProfile(userId: number): Promise<User> {
  return this.getPuri("r").table("users").where("id", userId).first();
}

// Frontend: Use auto-generated Hook
function UserProfile({ userId }: { userId: number }) {
  const { data: user, isLoading } = UserService.useUser("A", userId);

  if (isLoading) return <div>Loading...</div>;
  return <div>{user.username}</div>; // ✅ Perfect type safety
}

Optimization with Subsets

Query only the fields you need to reduce network costs and improve performance. Exact TypeScript types are automatically generated for each Subset.
// List view: minimal info only
const users = await UserService.findMany("A", { page: 1 });
// { id, email, username }

// Detail view: full info
const user = await UserService.findById("C", userId);
// { id, email, username, bio, createdAt, updatedAt, ... }

SSR

Supports server-side rendering based on Vite + React. Register SSR routes with registerSSR and preload necessary data on the server with the preload callback.
// api/src/ssr/routes.ts
import { registerSSR } from "sonamu/ssr";
import { UserService } from "../application/queries.generated";

// Register user detail page for SSR
registerSSR({
  path: "/users/:id",
  preload: (params) => [
    // Call Service method → Returns SSRQuery
    UserService.getUser("C", parseInt(params.id)),
  ],
});
Registered routes are rendered on the server and automatically hydrated on the client. Improve SEO optimization and initial loading speed. Key Features:
  • ✅ Backend changes detected immediately via compile errors
  • ✅ Clean structure with Namespace-based organization
  • ✅ TanStack Query auto-integration (caching, revalidation, optimistic updates)
  • ✅ Accurate type inference per Subset
  • ✅ SSR support for SEO optimization

Testing

Sonamu includes powerful built-in tools for testing.

Fixture System

Easily load pre-seeded test data from Fixture DB with createFixtureLoader.
// fixture.ts
import { createFixtureLoader } from "sonamu/test";
import { UserModel } from "../application/user/user.model";

export const loadFixtures = createFixtureLoader({
  adminUser: async () => UserModel.findById("A", 1),
  normalUser: async () => UserModel.findById("A", 2),
});

// Use in tests
import { loadFixtures } from "./fixture";

test("Admin can view all users", async () => {
  const f = await loadFixtures(["adminUser"]);

  expect(f.adminUser.role).toBe("admin");
});

Naite: Test Data Tracking & Visualization

Track and visualize all data changes during test execution. Debugging becomes much easier.
test("User creation flow", async () => {
  Naite.t("Initial state", { userCount: 0 });

  const user = await UserModel.create({
    email: "test@test.com",
    username: "testuser",
  });

  Naite.t("After user creation", { userCount: 1, userId: user.id });

  await UserModel.delete(user.id);

  Naite.t("After deletion", { userCount: 0 });
});

Automatic Transaction Rollback

Each test runs in an isolated Transaction and automatically rolls back. Test isolation is guaranteed and no data cleanup is needed.
test("Create user", async () => {
  await UserModel.create({ email: "test@test.com", ... });
  // Auto rollback after test ends → DB remains clean
});

test("Next test starts with clean DB", async () => {
  const users = await UserModel.findMany();
  expect(users).toHaveLength(0); // ✅ No data from previous test
});
Key Features:
  • ✅ Reuse test data with Fixtures
  • ✅ Visualize data changes with Naite
  • ✅ Isolated tests with automatic Transaction rollback

AI Ready

Sonamu natively supports features for the AI era.

Vector Search (pgvector)

Define AI embedding search as an Entity field and you’re done. Native support for pgvector.
// Define vector field in Entity
{
  "name": "embedding",
  "type": "vector",
  "dimensions": 1536
}

// Vector search with vectorSimilarity
const results = await this.getPuri("r")
  .table("documents")
  .select("id", "title", "content")
  .vectorSimilarity("embedding", queryEmbedding, "similarity")
  .orderBy("similarity", "desc")
  .limit(10);

AI SDK Integration

Seamlessly integrates with Vercel AI SDK. Streaming responses are simple.
@api({ httpMethod: "POST" })
async chat(message: string): Promise<StreamingResponse> {
  const result = await streamText({
    model: openai("gpt-4"),
    messages: [{ role: "user", content: message }],
  });

  return result.toTextStreamResponse();
}

SSE (Server-Sent Events)

Easily use SSE for real-time streaming with the @stream decorator.
import { stream } from "sonamu";
import { z } from "zod";

const ChatEventSchema = z.object({
  type: z.enum(["message", "done"]),
  content: z.string().optional(),
});

@stream({ eventSchema: ChatEventSchema })
async chat(message: string) {
  return async (emit) => {
    for (const chunk of await generateResponse(message)) {
      await emit({ type: "message", content: chunk });
    }
    await emit({ type: "done" });
  };
}

AI Agent Integration

Create Entities in natural language using AI chat in Sonamu UI.
💬 "Create an e-commerce order system. I need tables for users, products, orders, and order items."

→ 4 Entities are automatically generated including relationships.
Key Features:
  • ✅ Native pgvector support (vector search)
  • ✅ Seamless AI SDK integration (streaming)
  • ✅ Real-time events with SSE
  • ✅ Auto-generate Entities with AI Agent

Seamless DX

Sonamu is designed with developer experience as the top priority.

HMR (Hot Module Replacement)

When you modify code, it’s instantly reflected without server restart. Whether you modify Entity, Model, or API, 2 seconds is all you need.
# Traditional approach: Modify Entity → Restart server → Wait 30 seconds
# Sonamu approach: Modify Entity → Save → Reflected in 2 seconds ✅
# Console output example
🔄 Invalidated:
- src/application/user/user.model.ts (with 8 APIs)

 All files are synced!
If you make 50 modifications a day? Traditional approach wastes 25 minutes, Sonamu takes only 1.7 minutes.

Sonamu UI

Define Entities, manage Subsets, and run Migrations directly in the web UI. No need to edit JSON manually.

Fast Feedback Loop

Entity change → Auto-generate types/schemas → Re-register APIs → Update frontend Services — everything is automatic. Key Features:
  • ✅ Develop without server restart using HMR
  • ✅ Visual Entity management with Sonamu UI
  • ✅ Syncer auto-synchronizes all code
  • ✅ Detect type errors immediately on change

Production Ready

Sonamu includes powerful features built for production environments.

BentoCache: Multi-Layer Caching

Easily use memory + Redis multi-layer caching.
import { Sonamu } from "sonamu";

// L1 (memory) + L2 (Redis) caching
const user = await Sonamu.cache.getOrSet(
  `user:${userId}`,
  async () => {
    return await UserModel.findById("C", userId);
  },
  { ttl: "5m" } // 5 minute cache
);

Cache-Control: Response Caching

Set HTTP response caching strategies simply with decorators.
@api({
  httpMethod: "GET",
  cacheControl: { maxAge: 3600, sMaxAge: 7200 }
})
async getPublicData(): Promise<PublicData[]> {
  return await this.getPuri("r")
    .table("public_data")
    .where("public", true);
}

i18n: Type-Safe Internationalization

Entity title, prop.desc, and enumLabels are automatically extracted to the dictionary. Manage translations with the type-safe SD() function, and non-existent keys are caught at compile time.
import { SD } from "../i18n/sd.generated";

SD("common.save")         // "저장" (ko) / "Save" (en)
SD("error.typo")          // ❌ Compile error: key not found

SD("entity.User")         // "User" - Auto-extracted Entity title
SD("entity.User.email")   // "Email" - Auto-extracted prop.desc
SD.enumLabels("UserRole")["admin"]  // "Administrator"

Workflow: Complex Business Logic Management

Systematically manage complex multi-step business logic.
import { workflow } from "sonamu";

export const processOrder = workflow(
  {
    name: "process-order",
    version: "1.0",
  },
  async ({ input, step }) => {
    // Step 1: Validation
    await step
      .define({ name: "validate" }, async () => validateOrder(input))
      .run();

    // Step 2: Payment
    const payment = await step
      .define({ name: "charge" }, async () => chargePayment(input))
      .run();

    // Step 3: Create Order
    const order = await step
      .define({ name: "create-order" }, async () =>
        createOrderRecord(input, payment)
      )
      .run();

    // Step 4: Send Email
    await step
      .define({ name: "send-email" }, async () => sendConfirmationEmail(order))
      .run();

    return order;
  }
);

// Execute
await Sonamu.workflows.run(processOrder, orderData);

Built-in Performance Optimization

N+1 problem auto-resolution and query optimization are provided by default.
// N+1 auto-resolved in relation queries
const posts = await PostModel.findMany("A", {
  relations: ["author", "tags"], // Optimized with DataLoader pattern
});
Key Features:
  • ✅ Multi-layer caching with BentoCache (memory + Redis)
  • ✅ HTTP response caching with Cache-Control
  • ✅ Type-safe internationalization with i18n
  • ✅ Complex business logic management with Workflow
  • ✅ N+1 problem auto-resolution

Simple Example

The complete flow from defining an Entity to using it.
Note: Auto-generated files
  • api/src/application/sonamu.generated.ts - All types, schemas, Enums
  • web/src/services/sonamu.generated.ts - Frontend types (synchronized)
  • web/src/services/services.generated.ts - All Service methods
  • web/src/services/{entity}/{entity}.types.ts - Custom types (manually written)

1. Define Entity

{
  "id": "User",
  "table": "users",
  "props": [
    { "name": "email", "type": "string", "length": 255 },
    { "name": "username", "type": "string", "length": 100 },
    { "name": "role", "type": "enum", "id": "UserRole" }
  ]
}

2. Run Migration and Sync

Save Entity → Run Migration → pnpm sync: ✅ TypeScript types (sonamu.generated.ts)
✅ Zod schemas (BaseSchema, BaseListParams)
✅ DB migrations
✅ Frontend Service (services.generated.ts)
✅ TanStack Query Hooks

3. Write Business Logic in Model

@api({ httpMethod: "POST" })
@transactional()
async register(params: {
  email: string;
  username: string;
  password: string;
}): Promise<{ user: User }> {
  const wdb = this.getPuri("w");

  wdb.ubRegister("users", {
    email: params.email,
    username: params.username,
    password: await bcrypt.hash(params.password, 10),
    role: "user",
  });

  const [userId] = await wdb.ubUpsert("users");
  const user = await this.findById("C", userId);

  return { user };
}

4. Use Immediately in React

import { useTypeForm } from "@sonamu-kit/react-components/lib";
import { Input, Button } from "@sonamu-kit/react-components/components";
import { UserService } from "@/services/services.generated";
import { UserSaveParams } from "@/services/user/user.types";

function RegisterForm() {
  const { register, submit } = useTypeForm(UserSaveParams, {
    email: "",
    username: "",
    password: "",
  });

  const saveMutation = UserService.useSaveMutation();

  const handleSubmit = submit(async (form) => {
    saveMutation.mutate(
      { spa: [form] },
      {
        onSuccess: ([userId]) => {
          console.log("Registered:", userId); // ✅ Type safe
        },
      },
    );
  });

  return (
    <div>
      <Input placeholder="Email" {...register("email")} />
      <Input placeholder="Username" {...register("username")} />
      <Input type="password" placeholder="Password" {...register("password")} />
      <Button onClick={handleSubmit}>Register</Button>
    </div>
  );
}

Getting Started

3 minutes is all you need.
# Create project
pnpm create sonamu my-project

# Start development server
cd my-project/api
pnpm dev

# Open Sonamu UI
open http://localhost:1028/sonamu-ui

Community

GitHub

Issue reports and contributions

With Sonamu, developing in TypeScript becomes enjoyable.