Server Error Integration
15.3.1. Backend Error Response Format
The TQS standard backend error response follows the structure below. The frontend must handle errors according to this format.
typescript
// src/types/api.ts
interface ApiErrorResponse {
errorCode: string // Error code (e.g., 'VALIDATION_ERROR', 'CONFLICT')
message: string // User-facing message
fieldErrors?: { // Per-field validation errors (optional)
field: string
message: string
}[]
}| Field | Required | Description |
|---|---|---|
errorCode | Required | Error classification code |
message | Required | Global error message |
fieldErrors | Optional | List of field-level validation failures |
fieldErrorsis included only when server-side validation fails during form submission.errorCodeis used by the frontend for branching logic based on error type.
15.3.2. Per-Field Error Mapping
fieldErrors returned from the server must be mapped to the form's errors state.
typescript
// src/utils/mapServerErrors.ts
import type { ApiErrorResponse } from '@/types/api'
export function mapFieldErrors(
response: ApiErrorResponse
): Record<string, string> {
const errors: Record<string, string> = {}
if (response.fieldErrors) {
for (const fieldError of response.fieldErrors) {
errors[fieldError.field] = fieldError.message
}
}
return errors
}This is applied in the form submission handler as follows.
typescript
import { mapFieldErrors } from '@/utils/mapServerErrors'
import axios from 'axios'
async function handleSubmit() {
isSubmitting.value = true
errors.value = {}
try {
await createUser(form.value)
} catch (e) {
if (axios.isAxiosError(e) && e.response?.data) {
const serverError = e.response.data as ApiErrorResponse
errors.value = mapFieldErrors(serverError)
}
} finally {
isSubmitting.value = false
}
}- The server's
fieldvalues must match the keys of the form object. - Field naming conventions must be agreed upon between the backend and frontend in advance.
15.3.3. Global Error Handling
Errors that are not mapped to fieldErrors must be treated as global errors. They must be displayed as toast notifications or banners at the top of the form.
typescript
import { useToast } from '@/composables/useToast'
const { showToast } = useToast()
async function handleSubmit() {
isSubmitting.value = true
errors.value = {}
try {
await createUser(form.value)
} catch (e) {
if (axios.isAxiosError(e) && e.response?.data) {
const serverError = e.response.data as ApiErrorResponse
const fieldErrors = mapFieldErrors(serverError)
if (Object.keys(fieldErrors).length > 0) {
errors.value = fieldErrors
} else {
showToast({ type: 'error', message: serverError.message })
}
} else {
showToast({ type: 'error', message: 'An error occurred while processing the request.' })
}
} finally {
isSubmitting.value = false
}
}| Error Type | Display Method | Example |
|---|---|---|
| Field validation error | Inline below field | "This email is already in use." |
| Business logic error | Toast notification | "Daily registration limit exceeded." |
| Network/server error | Toast notification | "An error occurred while processing the request." |
15.3.4. Error Reset
When the user modifies a form, the related errors must be automatically cleared. All errors must be cleared upon resubmission.
typescript
import { watch } from 'vue'
// Clear the corresponding error when an individual field changes
watch(
() => form.value.email,
() => {
if (errors.value.email) {
delete errors.value.email
}
}
)
// General reset — watch the entire form
watch(
form,
() => {
errors.value = {}
},
{ deep: true }
)- Field-level clearing removes only the specific field's error, preserving error messages on other fields.
- Watching the entire form clears all errors at once, which is concise but should be noted that errors on other fields will also disappear.
- The appropriate strategy should be selected based on project requirements.