Skip to main content
findById is a method that retrieves a single record by ID. It loads data with the specified subset and throws a NotFoundException if the record doesn’t exist.
findById is not defined in BaseModelClass. It’s a standard pattern automatically generated by the Syncer in each Model class when you create an Entity.

Type Signature

async findById<T extends SubsetKey>(
  subset: T,
  id: number
): Promise<SubsetMapping[T]>

Auto-Generated Code

Sonamu automatically generates the following code based on your Entity:
// src/application/user/user.model.ts (auto-generated)
class UserModelClass extends BaseModelClass {
  @api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "User" })
  async findById<T extends UserSubsetKey>(
    subset: T,
    id: number
  ): Promise<UserSubsetMapping[T]> {
    const { rows } = await this.findMany(subset, {
      id,
      num: 1,
      page: 1,
    });
    if (!rows[0]) {
      throw new NotFoundException(`User ID ${id} not found`);
    }

    return rows[0];
  }
}
How it works:
  1. Calls findMany to query by ID
  2. Fetches only one record with num: 1, page: 1
  3. Throws NotFoundException if no result
  4. Returns the first record

Parameters

subset

Specifies the subset of data to retrieve. Type: SubsetKey (e.g., "A", "B", "C") Subsets determine the shape of data as defined in your Entity configuration. Each subset can include different fields and relationships.
// Fetch basic info only
const user = await UserModel.findById("A", 1);

// Fetch with relationship data
const user = await UserModel.findById("B", 1);

id

The ID of the record to retrieve. Type: number
const user = await UserModel.findById("A", 123);

Return Value

Type: Promise<SubsetMapping[T]> Returns a record of the specified subset type. The type is automatically inferred based on the subset.
// user's type is automatically inferred as UserSubsetMapping["A"]
const user = await UserModel.findById("A", 1);

console.log(user.id);      // number
console.log(user.email);   // string
console.log(user.name);    // string

Exceptions

NotFoundException

Throws NotFoundException if the record with the given ID doesn’t exist.
try {
  const user = await UserModel.findById("A", 999);
} catch (error) {
  if (error instanceof NotFoundException) {
    console.error("User not found");
  }
}
HTTP Status Code: 404

Basic Usage

Simple Query

import { UserModel } from "./user/user.model";

class UserService {
  async getUser(userId: number) {
    // Fetch user by ID (subset A)
    const user = await UserModel.findById("A", userId);

    return {
      id: user.id,
      email: user.email,
      name: user.name
    };
  }
}

Usage in API

findById is automatically exposed as a REST API with the @api decorator:
// Auto-generated code
@api({ httpMethod: "GET", clients: ["axios", "tanstack-query"], resourceName: "User" })
async findById<T extends UserSubsetKey>(
  subset: T,
  id: number
): Promise<UserSubsetMapping[T]> {
  // ...
}
Generated client code:
// services.generated.ts (auto-generated)
import { UserService } from "@/services/UserService";

// Fetch user by ID
const user = await UserService.getUser("A", 123);

Usage by Subset

Subset A (Basic)

const user = await UserModel.findById("A", 1);

// Available fields
user.id;        // number
user.email;     // string
user.name;      // string
user.created_at;// Date

Subset B (With Relationships)

const user = await UserModel.findById("B", 1);

// Relationship data auto-loaded
user.id;              // number
user.email;           // string
user.name;            // string
user.posts;           // Post[] (1:N relationship)
user.profile;         // Profile (1:1 relationship)

Subset C (Nested Relationships)

const user = await UserModel.findById("C", 1);

// Nested relationships loaded
user.posts[0].comments;   // Comment[] (nested relationship)
user.profile.images;      // Image[] (nested relationship)

Practical Examples

import { UserModel } from "./user/user.model";
import { api } from "sonamu";

class ProfileFrame {
  @api({ httpMethod: "GET" })
  async getProfile(userId: number) {
    // Fetch user by ID (with profile info)
    const user = await UserModel.findById("Profile", userId);

    return {
      id: user.id,
      email: user.email,
      name: user.name,
      avatar: user.profile?.avatar_url,
      bio: user.profile?.bio,
      posts_count: user.posts?.length ?? 0
    };
  }
}

findById vs findOne vs findMany

findById

  • Fetches single record by ID
  • Throws exception if record not found
  • Simplest and fastest
  • Auto-generated
// Query by ID (throws if not found)
const user = await UserModel.findById("A", 1);

findOne

  • Fetches single record by conditions
  • Returns null if record not found
  • Supports complex conditions
  • Must be implemented manually (optional)
// Query by conditions (returns null if not found)
const user = await UserModel.findOne("A", {
  email: "user@example.com"
});

findMany

  • Fetches multiple records by conditions
  • Supports pagination
  • Returns total count
  • Auto-generated
// List query (with pagination)
const { rows, total } = await UserModel.findMany("A", {
  status: "active",
  num: 20,
  page: 1
});

Type Safety

Return types are automatically inferred based on the subset:
// Subset A: basic fields only
const userA = await UserModel.findById("A", 1);
userA.posts;  // ❌ Type error: Property 'posts' does not exist

// Subset B: includes posts
const userB = await UserModel.findById("B", 1);
userB.posts;  // βœ… Post[]

Client Usage

React (TanStack Query)

import { useQuery } from "@tanstack/react-query";
import { UserService } from "@/services/UserService";

function UserProfile({ userId }: { userId: number }) {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ["user", userId],
    queryFn: () => UserService.getUser("A", userId)
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>User not found</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Vue

import { UserService } from "@/services/UserService";

export default {
  data() {
    return {
      user: null,
      loading: false,
      error: null
    };
  },
  async mounted() {
    this.loading = true;
    try {
      this.user = await UserService.getUser("A", this.userId);
    } catch (error) {
      this.error = error.message;
    } finally {
      this.loading = false;
    }
  }
};

Cautions

1. Subset Selection

Choose subsets that include only the necessary data. Loading unnecessary relationships degrades performance.
// ❌ Bad: loads all relationships
const user = await UserModel.findById("Full", 1);

// βœ… Good: only necessary fields
const user = await UserModel.findById("A", 1);

2. Exception Handling

findById throws an exception if the record doesn’t exist. Add exception handling when necessary.
// ❌ Bad: no exception handling
const user = await UserModel.findById("A", userId);  // Can throw 404 error

// βœ… Good: with exception handling
try {
  const user = await UserModel.findById("A", userId);
} catch (error) {
  if (error instanceof NotFoundException) {
    // Handle appropriately
  }
}

3. Null Possibility

If record existence is uncertain, use findOne (requires manual implementation).
// findById: record must exist
const user = await UserModel.findById("A", 1);

// findOne: record may not exist (requires manual implementation)
const user = await UserModel.findOne("A", { email: "unknown@example.com" });
if (user === null) {
  // Handle missing record
}

Next Steps