Problem
The FluentMeet API currently has no centralized error handling strategy. Errors from different layers of the application (validation, authentication, business logic, unhandled exceptions) return inconsistent response shapes, making it difficult for frontend clients to parse and display error messages reliably. This inconsistency complicates debugging, increases client-side boilerplate, and provides a poor developer experience.
Proposed Solution
Implement a uniform error handling system by defining a standard error response schema, a hierarchy of custom application exceptions, and a set of global exception handlers registered with FastAPI. All API errors—whether from request validation, custom business logic, or unexpected failures—will be caught and transformed into a single, predictable JSON structure before being returned to the client.
Standard Error Response Format
{
"status": "error",
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": []
}
User Stories
- As a frontend developer, I want every API error response to follow the same JSON structure, so I can build a single, reusable error-handling utility on the client side.
- As a backend developer, I want to throw descriptive custom exceptions from anywhere in the codebase and have them automatically formatted into a standard response, so I don't have to manually construct error responses in every endpoint.
- As a DevOps engineer, I want unhandled exceptions to be logged with full tracebacks on the server while returning only a generic error message to the client, so sensitive internal details are never leaked.
Acceptance Criteria
- A base
FluentMeetException class is defined in app/core/exceptions.py, along with subclasses for common HTTP error scenarios (BadRequestException, UnauthorizedException, ForbiddenException, NotFoundException, ConflictException, InternalServerException).
- A standard
ErrorResponse Pydantic model is defined in app/core/error_responses.py with fields: status, code, message, and details.
- Global exception handlers are implemented in
app/core/exception_handlers.py to catch:
FluentMeetException → returns the exception's status code, error code, message, and details.
RequestValidationError → returns 400 Bad Request with field-specific error details.
HTTPException → wraps FastAPI/Starlette HTTP exceptions in the standard format.
- Unhandled
Exception → returns 500 Internal Server Error with a generic message and logs the traceback server-side.
- All exception handlers are registered in
app/main.py via a register_exception_handlers(app) call.
- The following HTTP status codes are used consistently:
400 Bad Request: Validation or business logic errors.
401 Unauthorized: Authentication issues.
403 Forbidden: Permission issues or soft-deleted user access.
404 Not Found: Resource does not exist.
409 Conflict: Duplicate resource (e.g., email already registered).
500 Internal Server Error: Unexpected server failures.
- Existing endpoints (e.g., the health check) remain unaffected.
- Unit tests verify the correct status code and JSON shape for every handler.
Proposed Technical Details
app/core/exceptions.py: Define FluentMeetException(Exception) with status_code, code, message, and details attributes. Create subclasses that pre-set status_code and a default code string (e.g., NotFoundException defaults to 404 and "NOT_FOUND").
app/core/error_responses.py: Define ErrorDetail(BaseModel) with optional field and required message, and ErrorResponse(BaseModel) with status="error", code, message, and details: list[ErrorDetail]. Include a create_error_response() helper that returns a JSONResponse.
app/core/exception_handlers.py: Implement four handler functions and a register_exception_handlers(app) function that calls app.add_exception_handler(...) for each.
app/main.py: Import and call register_exception_handlers(app) after the FastAPI() instance is created.
tests/test_error_handling.py: Use TestClient with temporary route injection to trigger each exception type and assert on the response status code and JSON body.
Tasks
Open Questions/Considerations
- Should the
details field in the error response include a request/correlation ID for tracing purposes?
- Should rate-limiting errors (
429 Too Many Requests) be included in the custom exception hierarchy now or deferred to a later issue?
- For validation errors, should the field path use dot notation (e.g.,
body.email) or just the field name (e.g., email)?
Problem
The FluentMeet API currently has no centralized error handling strategy. Errors from different layers of the application (validation, authentication, business logic, unhandled exceptions) return inconsistent response shapes, making it difficult for frontend clients to parse and display error messages reliably. This inconsistency complicates debugging, increases client-side boilerplate, and provides a poor developer experience.
Proposed Solution
Implement a uniform error handling system by defining a standard error response schema, a hierarchy of custom application exceptions, and a set of global exception handlers registered with FastAPI. All API errors—whether from request validation, custom business logic, or unexpected failures—will be caught and transformed into a single, predictable JSON structure before being returned to the client.
Standard Error Response Format
{ "status": "error", "code": "ERROR_CODE", "message": "Human-readable error message", "details": [] }User Stories
Acceptance Criteria
FluentMeetExceptionclass is defined inapp/core/exceptions.py, along with subclasses for common HTTP error scenarios (BadRequestException,UnauthorizedException,ForbiddenException,NotFoundException,ConflictException,InternalServerException).ErrorResponsePydantic model is defined inapp/core/error_responses.pywith fields:status,code,message, anddetails.app/core/exception_handlers.pyto catch:FluentMeetException→ returns the exception's status code, error code, message, and details.RequestValidationError→ returns400 Bad Requestwith field-specific error details.HTTPException→ wraps FastAPI/Starlette HTTP exceptions in the standard format.Exception→ returns500 Internal Server Errorwith a generic message and logs the traceback server-side.app/main.pyvia aregister_exception_handlers(app)call.400 Bad Request: Validation or business logic errors.401 Unauthorized: Authentication issues.403 Forbidden: Permission issues or soft-deleted user access.404 Not Found: Resource does not exist.409 Conflict: Duplicate resource (e.g., email already registered).500 Internal Server Error: Unexpected server failures.Proposed Technical Details
app/core/exceptions.py: DefineFluentMeetException(Exception)withstatus_code,code,message, anddetailsattributes. Create subclasses that pre-setstatus_codeand a defaultcodestring (e.g.,NotFoundExceptiondefaults to404and"NOT_FOUND").app/core/error_responses.py: DefineErrorDetail(BaseModel)with optionalfieldand requiredmessage, andErrorResponse(BaseModel)withstatus="error",code,message, anddetails: list[ErrorDetail]. Include acreate_error_response()helper that returns aJSONResponse.app/core/exception_handlers.py: Implement four handler functions and aregister_exception_handlers(app)function that callsapp.add_exception_handler(...)for each.app/main.py: Import and callregister_exception_handlers(app)after theFastAPI()instance is created.tests/test_error_handling.py: UseTestClientwith temporary route injection to trigger each exception type and assert on the response status code and JSON body.Tasks
app/core/exceptions.pywithFluentMeetExceptionand all subclasses.app/core/error_responses.pywithErrorResponse,ErrorDetail, and thecreate_error_response()helper.app/core/exception_handlers.pywith all four global handlers and theregister_exception_handlers()function.app/main.pyto import and callregister_exception_handlers(app).tests/test_error_handling.pywith tests for every exception handler.Open Questions/Considerations
detailsfield in the error response include a request/correlation ID for tracing purposes?429 Too Many Requests) be included in the custom exception hierarchy now or deferred to a later issue?body.email) or just the field name (e.g.,email)?