> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# BadRequestException

> 400 Exception for Handling Invalid Requests

`BadRequestException` is an exception used when a client's request is invalid. It returns an HTTP 400 status code and is used for validation failures, invalid parameters, malformed requests, etc.

## Basic Usage

```typescript theme={null}
class BadRequestException extends SoException {
  constructor(message: LocalizedString, payload?: unknown);
}
```

**Simple Example:**

```typescript theme={null}
@api()
async createPost(title: string, content: string) {
  if (!title || title.trim().length === 0) {
    throw new BadRequestException("Title is required");
  }

  if (content.length > 10000) {
    throw new BadRequestException("Content cannot exceed 10,000 characters");
  }

  return this.create({ title, content });
}
```

## Practical Examples

### Validation

```typescript theme={null}
@api()
async updateUser(
  userId: number,
  email?: string,
  age?: number,
  phoneNumber?: string
) {
  const errors: Array<{ field: string; message: string }> = [];

  // Email validation
  if (email && !email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
    errors.push({
      field: "email",
      message: "Invalid email format"
    });
  }

  // Age validation
  if (age !== undefined && (age < 0 || age > 150)) {
    errors.push({
      field: "age",
      message: "Age must be between 0 and 150"
    });
  }

  // Phone number validation
  if (phoneNumber && !phoneNumber.match(/^\d{3}-\d{3,4}-\d{4}$/)) {
    errors.push({
      field: "phoneNumber",
      message: "Invalid phone number format (e.g., 010-1234-5678)"
    });
  }

  if (errors.length > 0) {
    throw new BadRequestException("Invalid input data", {
      errors
    });
  }

  return this.update(userId, { email, age, phoneNumber });
}
```

### Business Rule Validation

```typescript theme={null}
@api()
async transferMoney(
  ctx: Context,
  fromAccountId: number,
  toAccountId: number,
  amount: number
) {
  // Amount validation
  if (amount <= 0) {
    throw new BadRequestException(
      "Transfer amount must be greater than 0",
      { amount }
    );
  }

  if (amount > 10000000) {
    throw new BadRequestException(
      "Single transfer limit is 10 million",
      { amount, limit: 10000000 }
    );
  }

  // Account validation
  if (fromAccountId === toAccountId) {
    throw new BadRequestException("Cannot transfer to the same account");
  }

  const fromAccount = await AccountModel.findById(fromAccountId);

  if (fromAccount.userId !== ctx.user!.id) {
    throw new BadRequestException("Can only transfer from your own account");
  }

  if (fromAccount.balance < amount) {
    throw new BadRequestException(
      "Insufficient balance",
      {
        balance: fromAccount.balance,
        requested: amount,
        shortage: amount - fromAccount.balance
      }
    );
  }

  // Execute transfer
  return this.executeTransfer(fromAccountId, toAccountId, amount);
}
```

### File Upload Validation

```typescript theme={null}
@upload()
async uploadProfileImage(ctx: Context) {
  const { bufferedFiles } = Sonamu.getContext();
  const file = bufferedFiles?.[0]; // Use first file

  if (!file) {
    throw new BadRequestException("File is required");
  }

  // File size validation (5MB)
  const maxSize = 5 * 1024 * 1024;
  if (file.size > maxSize) {
    throw new BadRequestException(
      "File size cannot exceed 5MB",
      {
        size: file.size,
        maxSize,
        sizeMB: (file.size / 1024 / 1024).toFixed(2),
        maxSizeMB: 5
      }
    );
  }

  // File type validation
  const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
  if (!allowedTypes.includes(file.mimetype)) {
    throw new BadRequestException(
      "Unsupported image format",
      {
        provided: file.mimetype,
        allowed: allowedTypes
      }
    );
  }

  // Save image
  const key = `profile-images/${Date.now()}-${file.filename}`;
  return file.saveToDisk("s3", key);
}
```

### Date/Time Validation

```typescript theme={null}
@api()
async createReservation(
  userId: number,
  startDate: Date,
  endDate: Date,
  guestCount: number
) {
  const now = new Date();

  // Check for past dates
  if (startDate < now) {
    throw new BadRequestException(
      "Reservation start date must be after current time",
      { startDate, now }
    );
  }

  // Check start/end date order
  if (startDate >= endDate) {
    throw new BadRequestException(
      "End date must be after start date",
      { startDate, endDate }
    );
  }

  // Check maximum reservation period (30 days)
  const maxDays = 30;
  const daysDiff = Math.ceil(
    (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)
  );

  if (daysDiff > maxDays) {
    throw new BadRequestException(
      `Maximum reservation period is ${maxDays} days`,
      { requestedDays: daysDiff, maxDays }
    );
  }

  // Guest count check
  if (guestCount < 1 || guestCount > 10) {
    throw new BadRequestException(
      "Guest count must be between 1 and 10",
      { guestCount }
    );
  }

  return this.create({ userId, startDate, endDate, guestCount });
}
```

### Array/Object Validation

```typescript theme={null}
@api()
async createOrder(
  userId: number,
  items: Array<{ productId: number; quantity: number }>
) {
  // Empty order check
  if (!items || items.length === 0) {
    throw new BadRequestException("At least one product is required");
  }

  // Maximum order quantity check
  if (items.length > 50) {
    throw new BadRequestException(
      "Maximum of 50 products per order",
      { itemCount: items.length, maxItems: 50 }
    );
  }

  // Validate each item
  const errors: Array<{ index: number; message: string }> = [];

  items.forEach((item, index) => {
    if (!item.productId || item.productId <= 0) {
      errors.push({
        index,
        message: "Invalid product ID"
      });
    }

    if (!item.quantity || item.quantity <= 0) {
      errors.push({
        index,
        message: "Quantity must be at least 1"
      });
    }

    if (item.quantity > 999) {
      errors.push({
        index,
        message: "Maximum 999 items per product"
      });
    }
  });

  if (errors.length > 0) {
    throw new BadRequestException("Invalid order items", {
      errors
    });
  }

  return this.createOrder(userId, items);
}
```

## Combining with Zod Validation

Sonamu automatically validates API parameters with Zod schemas, but use `BadRequestException` for additional business logic validation:

```typescript theme={null}
@api()
async createProduct(
  name: string,        // Auto-validated by Zod: string type
  price: number,       // Auto-validated by Zod: number type
  categoryId: number   // Auto-validated by Zod: number type
) {
  // Additional business logic validation
  if (price < 0) {
    throw new BadRequestException("Price must be 0 or greater");
  }

  const category = await CategoryModel.findById(categoryId);
  if (!category) {
    throw new BadRequestException(
      "Category does not exist",
      { categoryId }
    );
  }

  if (!category.isActive) {
    throw new BadRequestException(
      "Cannot add products to inactive category",
      { categoryId, categoryName: category.name }
    );
  }

  return this.create({ name, price, categoryId });
}
```

## Payload Usage Patterns

### Field-level Error List

```typescript theme={null}
throw new BadRequestException("Input validation failed", {
  errors: [
    { field: "email", code: "INVALID_FORMAT" },
    { field: "password", code: "TOO_SHORT", minLength: 8 },
  ],
});
```

### Limit Information

```typescript theme={null}
throw new BadRequestException("File size exceeded", {
  size: file.size,
  limit: 5242880, // 5MB in bytes
  unit: "bytes",
});
```

### Retry Information

```typescript theme={null}
throw new BadRequestException("Daily request limit exceeded", {
  limit: 100,
  used: 100,
  resetsAt: new Date("2024-01-01T00:00:00Z"),
});
```

## Client Response Examples

### Basic Response

```json theme={null}
{
  "statusCode": 400,
  "message": "Title is required"
}
```

### Response with Payload

```json theme={null}
{
  "statusCode": 400,
  "message": "Invalid input data",
  "payload": {
    "errors": [
      {
        "field": "email",
        "message": "Invalid email format"
      },
      {
        "field": "age",
        "message": "Age must be between 0 and 150"
      }
    ]
  }
}
```

## Related Documentation

* [SoException](/en/api-reference/exceptions/sonamu-error)
* [Custom Exceptions](/en/api-reference/exceptions/custom-errors)
* [Zod Validation](/en/api-development/validation)
