Handlers that return ad-hoc inline objects — { id: user.id, name: user.name, role: user.role } — with no shared type definition silently expose database columns that were never intended for the API surface. Without explicit response types, a refactor that renames a database column silently changes the API contract, and TypeScript's type system provides zero protection. iso-25010:2011 maintainability.modifiability and compatibility.interoperability both degrade: the API shape can change with any database migration and no tooling catches it.
High because untyped responses expose accidental data leakage paths and remove TypeScript's protection against API shape changes caused by database refactors.
Define explicit TypeScript interfaces for every API response shape and use a dedicated serialization function to enforce them:
interface UserResponse {
id: string
name: string
email: string
isActive: boolean
createdAt: string
}
function toUserResponse(user: DbUser): UserResponse {
return {
id: user.id,
name: user.name,
email: user.email,
isActive: user.is_active,
createdAt: user.created_at.toISOString(),
}
}
The serialization function is the explicit allowlist of what leaves the database layer. Any new field must be added deliberately, not by accident.
ID: api-design.contract-quality.response-types
Severity: high
What to look for: Check whether response objects have explicit type definitions. For TypeScript: look for interface/type declarations for API responses, or Zod schemas used for both validation and response typing. For Python: Pydantic response models, TypedDict, or dataclass-based responses. For Go: struct types for JSON marshaling. Look for anti-patterns: returning raw database rows without a DTO layer, using any or untyped objects for responses, constructing response objects inline with no type safety.
Pass criteria: API response objects have explicit type definitions (TypeScript interfaces, Pydantic models, Go structs, etc.). Response shapes are predictable and documented through types. Handlers return typed objects, not raw database rows or untyped dictionaries. Enumerate all handler files and classify each response as typed or untyped. At least 80% of endpoints must have explicitly typed responses.
Fail criteria: Response objects are constructed ad-hoc with no type definitions. Handlers return any-typed objects, spread raw database rows into responses, or construct response objects inline without a shared type. Should not pass when handlers use as any type assertions to bypass type checking on responses.
Skip (N/A) when: The API is written in plain JavaScript with no TypeScript, and no type annotation system is used. Also skip for languages where typing is optional and not adopted in the project.
Detail on fail: Describe what's missing (e.g., "No TypeScript interfaces for any response objects. Handlers return inline objects like { id: user.id, name: user.name } with no shared type. 8 of 12 endpoints return untyped responses."). Max 500 chars.
Remediation: Define explicit types for every API response shape:
// Define response types:
interface UserResponse {
id: string
name: string
email: string
isActive: boolean
createdAt: string
}
// Use in handler -- type safety prevents accidental field exposure:
function toUserResponse(user: DbUser): UserResponse {
return {
id: user.id,
name: user.name,
email: user.email,
isActive: user.is_active,
createdAt: user.created_at.toISOString(),
}
}