Learn how to consistently handle errors in APIs and provide clear responses.
Error Handling Overview
Consistent Responses Standardized error format Client-friendly
Status Codes HTTP status codes RESTful standard
Clear Messages Developer-friendly User-friendly
Logging Error tracking Debugging support
SoException
Sonamu provides error classes based on SoException. When you throw a SoException, the framework automatically sends an appropriate HTTP status code and structured error response.
Basic Usage
import { NotFoundException } from "sonamu" ;
class UserModel extends BaseModelClass {
@ api ({ httpMethod: "GET" })
async get ( id : number ) : Promise < User > {
const rdb = this . getPuri ( "r" );
const user = await rdb . table ( "users" ). where ( "id" , id ). first ();
if ( ! user ) {
throw new NotFoundException ( "User not found" );
}
return user ;
}
}
Available Exception Classes
These are the exception classes provided by Sonamu. All inherit from SoException.
Class Status Code When to Use BadRequestException400 Invalid parameters or malformed request UnauthorizedException401 Authentication required or access denied NotFoundException404 Accessing a non-existent record InternalServerErrorException500 Internal processing or external API call error ServiceUnavailableException503 Processing not possible in current state TargetNotFoundException520 Processing target does not exist AlreadyProcessedException541 Request has already been processed DuplicateRowException542 Duplicate request where duplicates are not allowed
SoException Constructor
All exception classes share the same constructor signature.
constructor ( message : LocalizedString , payload ?: unknown )
message: Error message. Supports localized strings (LocalizedString).
payload: Additional information. Can pass Zod validation issue arrays, etc.
Usage Examples
Various Error Situations
import {
BadRequestException ,
UnauthorizedException ,
NotFoundException ,
DuplicateRowException ,
} from "sonamu" ;
class UserModel extends BaseModelClass {
@ api ({ httpMethod: "GET" })
async get ( id : number ) : Promise < User > {
const rdb = this . getPuri ( "r" );
const user = await rdb . table ( "users" ). where ( "id" , id ). first ();
if ( ! user ) {
throw new NotFoundException ( "User not found" );
}
return user ;
}
@ api ({ httpMethod: "POST" })
async create ( params : CreateUserParams ) : Promise <{ userId : number }> {
const context = Sonamu . getContext ();
// Check authentication
if ( ! context . user ) {
throw new UnauthorizedException ( "Authentication required" );
}
// Input validation
if ( ! params . email ) {
throw new BadRequestException ( "Email is required" );
}
const rdb = this . getPuri ( "r" );
// Check duplicate
const existing = await rdb . table ( "users" ). where ( "email" , params . email ). first ();
if ( existing ) {
throw new DuplicateRowException ( "Email already exists" );
}
const wdb = this . getPuri ( "w" );
const [ user ] = await wdb . table ( "users" ). insert ( params ). returning ({ id: "id" });
return { userId: user . id };
}
}
Passing Detailed Info via payload
import { BadRequestException } from "sonamu" ;
class OrderModel extends BaseModelClass {
@ api ({ httpMethod: "POST" })
async create ( params : CreateOrderParams ) : Promise <{ orderId : number }> {
// Pass error details via payload
if ( params . quantity <= 0 ) {
throw new BadRequestException ( "Invalid order quantity" , {
field: "quantity" ,
value: params . quantity ,
constraint: "must be > 0" ,
});
}
// ...
}
}
isSoException Type Guard
import { isSoException , AlreadyProcessedException } from "sonamu" ;
class PaymentModel extends BaseModelClass {
@ api ({ httpMethod: "POST" })
async process ( paymentId : number ) : Promise < void > {
try {
await this . executePayment ( paymentId );
} catch ( error ) {
if ( isSoException ( error ) && error . statusCode === 541 ) {
// Already processed payment - ignore
return ;
}
throw error ;
}
}
}
Zod Validation Error Handling
When you pass a Zod issue array to BadRequestException’s payload, Sonamu’s error handler automatically includes validation error details in the response.
Converting Zod Errors
import { z } from "zod" ;
import { BadRequestException } from "sonamu" ;
const CreateUserSchema = z . object ({
email: z . string (). email (),
name: z . string (). min ( 2 ),
password: z . string (). min ( 8 ),
});
class UserModel extends BaseModelClass {
@ api ({ httpMethod: "POST" })
async create ( params : unknown ) : Promise <{ userId : number }> {
const result = CreateUserSchema . safeParse ( params );
if ( ! result . success ) {
throw new BadRequestException ( "Validation failed" , result . error . issues );
}
const validated = result . data ;
const wdb = this . getPuri ( "w" );
const [ user ] = await wdb . table ( "users" ). insert ( validated ). returning ({ id: "id" });
return { userId: user . id };
}
}
Sonamu’s built-in error handler responds in the following format.
Basic Error Response
{
"name" : "NotFoundException" ,
"code" : null ,
"message" : "User not found"
}
Zod Validation Error Response (when issue array is passed as payload)
{
"name" : "BadRequestException" ,
"code" : null ,
"message" : "Validation failed (email)" ,
"issues" : [
{
"code" : "invalid_type" ,
"expected" : "string" ,
"received" : "undefined" ,
"path" : [ "email" ],
"message" : "Required"
}
]
}
Customizing the Error Handler
Sonamu includes a built-in error handler, but you can set a custom error handler via the lifecycle.onError option in startServer if needed.
import { Sonamu , isSoException } from "sonamu" ;
Sonamu . startServer ({
lifecycle: {
onError : ( error , request , reply ) => {
// Custom error handling logic
if ( isSoException ( error )) {
reply . status ( error . statusCode ). send ({
error: error . message ,
payload: error . payload ,
});
} else {
reply . status ( 500 ). send ({
error: "Internal server error" ,
});
}
},
},
});
Status Code Reference
Standard HTTP Status Codes
Code Name When to Use 200 OK Success (GET, PUT) 201 Created Resource created (POST) 204 No Content Success, no response (DELETE) 400 Bad Request Invalid request format 401 Unauthorized Authentication required 404 Not Found Resource not found 500 Internal Server Error Internal server error 503 Service Unavailable Service unavailable
Sonamu Custom Status Codes
Code Exception Class When to Use 520 TargetNotFoundExceptionProcessing target does not exist 541 AlreadyProcessedExceptionRequest already processed 542 DuplicateRowExceptionDuplicate where duplicates not allowed
Practical Pattern
import {
UnauthorizedException ,
BadRequestException ,
DuplicateRowException ,
} from "sonamu" ;
class UserModel extends BaseModelClass {
@ api ({ httpMethod: "POST" })
async create ( params : unknown ) : Promise <{ userId : number }> {
const context = Sonamu . getContext ();
// 1. Check authentication
if ( ! context . user ) {
throw new UnauthorizedException ( "Authentication required" );
}
// 2. Zod validation
const validated = CreateUserSchema . safeParse ( params );
if ( ! validated . success ) {
throw new BadRequestException ( "Validation failed" , validated . error . issues );
}
const data = validated . data ;
// 3. Business rule validation
const rdb = this . getPuri ( "r" );
const existing = await rdb . table ( "users" ). where ( "email" , data . email ). first ();
if ( existing ) {
throw new DuplicateRowException ( "Email already exists" );
}
// 4. Create
const wdb = this . getPuri ( "w" );
const [ user ] = await wdb . table ( "users" ). insert ( data ). returning ({ id: "id" });
return { userId: user . id };
}
}
Cautions
Cautions for error handling : 1. Never expose sensitive information (stack traces, DB errors,
etc.) 2. Use appropriate HTTP status codes 3. Provide clear and consistent error messages 4.
Always log errors 5. Limit detailed information in production
Next Steps
Automatic Validation Zod-based validation
Custom Validation Custom validation logic
@api Decorator API basic usage