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:
Calls findMany to query by ID
Fetches only one record with num: 1, page: 1
Throws NotFoundException if no result
Returns the first record
Parameters
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 );
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
User Profile
Order Details
Permission Check
Error Handling
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
};
}
}
import { OrderModel } from "./order/order.model" ;
import { api } from "sonamu" ;
class OrderFrame {
@ api ({ httpMethod: "GET" })
async getOrderDetail ( orderId : number ) {
// Fetch order details (with products, shipping info)
const order = await OrderModel . findById ( "Detail" , orderId );
return {
id: order . id ,
status: order . status ,
total_amount: order . total_amount ,
items: order . items . map ( item => ({
product_name: item . product . name ,
quantity: item . quantity ,
price: item . price
})),
shipping: {
address: order . shipping_address ,
status: order . shipping_status ,
tracking_number: order . tracking_number
},
customer: {
name: order . user . name ,
email: order . user . email
}
};
}
}
import { UserModel } from "./user/user.model" ;
import { Sonamu , api , UnauthorizedException } from "sonamu" ;
class PostFrame {
@ api ({ httpMethod: "POST" })
async deletePost ( postId : number ) {
const { user } = Sonamu . getContext ();
// Check post owner
const post = await PostModel . findById ( "A" , postId );
if ( post . user_id !== user . id ) {
throw new UnauthorizedException ( "You can only delete your own posts" );
}
await PostModel . del ([ postId ]);
return { success: true };
}
}
import { UserModel } from "./user/user.model" ;
import { NotFoundException , api } from "sonamu" ;
class UserFrame {
@ api ({ httpMethod: "GET" })
async getUserSafely ( userId : number ) {
try {
// Fetch user by ID
const user = await UserModel . findById ( "A" , userId );
return {
exists: true ,
user: {
id: user . id ,
name: user . name ,
email: user . email
}
};
} catch ( error ) {
if ( error instanceof NotFoundException ) {
return {
exists: false ,
user: null
};
}
throw error ;
}
}
@ api ({ httpMethod: "GET" })
async getMultipleUsers ( userIds : number []) {
const users = [];
for ( const id of userIds ) {
try {
const user = await UserModel . findById ( "A" , id );
users . push ( user );
} catch ( error ) {
// Skip non-existent users
if ( error instanceof NotFoundException ) {
continue ;
}
throw error ;
}
}
return users ;
}
}
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 >
);
}
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