How Server Error Envelopes Are Structured and Validated in Instatic
Instatic wraps every failed HTTP response in a TypeBox-validated JSON envelope with an optional error field, extracting human-readable messages through readEnvelope and responseErrorMessage utilities in src/core/http/apiClient.ts.
Instatic implements a consistent, type-safe approach to HTTP error handling using server error envelopes—minimal JSON structures that standardize failure responses across the application. Every API client in the repository relies on a single TypeBox schema defined in src/core/http/apiClient.ts to parse and validate these envelopes. This design ensures that UI components receive predictable error messages while maintaining strict runtime type safety.
Error Envelope Structure and Schema Definition
The canonical envelope shape follows a simple contract: an optional error property that may contain any JSON value, though typically a descriptive string. In src/core/http/apiClient.ts, the TypeBox schema definition looks like this:
const ErrorEnvelopeSchema = Type.Object({
error: Type.Optional(Type.Unknown()),
})
This schema permits flexibility in error payloads while providing compile-time and runtime validation. The Type.Optional wrapper ensures that responses missing the error field still pass validation, allowing the system to gracefully handle malformed or unexpected server responses.
The Validation Pipeline with readEnvelope
The core validation logic resides in the readEnvelope function, which clones the Response, parses the JSON body, and validates it against the supplied schema. If validation fails or the response is not OK, the utility throws an ApiError containing the extracted message.
The function signature follows this pattern:
readEnvelope<T>(res: Response, Schema: TSchema, fallbackMessage: string): Promise<Static<T>>
When processing error responses, callers pass ErrorEnvelopeSchema to readEnvelope. The utility parses the body via parseJsonResponse, validates the structure against the TypeBox schema, and either returns the typed payload or throws an ApiError initialized with the fallback message.
Extracting Human-Readable Messages
For UI-level error handling, Instatic provides responseErrorMessage, a specialized helper that guarantees a string output regardless of the envelope's contents. Located alongside readEnvelope in src/core/http/apiClient.ts, this function implements the following fallback hierarchy:
- Extract the
errorproperty if present and valid - Fall back to the raw response text body
- Use the provided fallback string if all else fails
This ensures that toast notifications and error boundaries always receive a usable string rather than undefined or complex objects.
Practical Implementation Examples
The following patterns demonstrate how server error envelopes integrate into real Instatic workflows.
Automatic Error Handling in API Requests
Higher-level utilities like apiRequest abstract envelope parsing entirely. When calling endpoints, developers supply a success schema while the client automatically handles error envelopes:
import { apiRequest } from '@core/http'
import { UserSchema } from '@/src/core/persistence/cmsUsers'
async function loadCurrentUser() {
// Returns Static<typeof UserSchema> on success
// Throws ApiError with envelope message on HTTP error
return await apiRequest('/admin/api/cms/user', {
schema: UserSchema,
})
}
Manual Envelope Parsing
For low-level fetch operations, manually invoke readEnvelope and responseErrorMessage:
import { readEnvelope, responseErrorMessage, ApiError } from '@core/http'
async function fetchWithRawError(url: string) {
const res = await fetch(url, { credentials: 'include' })
if (!res.ok) {
const msg = await responseErrorMessage(res, `Request failed: ${res.status}`)
throw new ApiError(msg, res.status)
}
return await readEnvelope(res, SomeSuccessSchema, 'Unable to decode response')
}
Unit Testing Error Scenarios
The test suite in src/__tests__/http/apiClient.test.ts validates envelope extraction and fallback behavior:
import { jsonResponse, responseErrorMessage } from '@core/http'
test('responseErrorMessage extracts envelope error', async () => {
const errRes = jsonResponse({ error: 'boom' }, 500)
expect(await responseErrorMessage(errRes, 'fallback')).toBe('boom')
})
Summary
- Server error envelopes in Instatic follow a minimal JSON structure with an optional
errorfield defined byErrorEnvelopeSchemainsrc/core/http/apiClient.ts. - TypeBox validation occurs through the
readEnvelopeutility, which parses responses and throwsApiErrorinstances for invalid or failed requests. - Message extraction is handled by
responseErrorMessage, ensuring UI components always receive string-based error descriptions through a reliable fallback chain. - Test coverage in
src/__tests__/http/apiClient.test.tsguarantees envelope parsing resilience against malformed JSON or missing bodies.
Frequently Asked Questions
What is the exact TypeBox schema for Instatic's server error envelopes?
The schema is defined in src/core/http/apiClient.ts as Type.Object({ error: Type.Optional(Type.Unknown()) }), allowing an optional error property containing any JSON value while maintaining runtime type safety through TypeBox validation.
How does Instatic handle malformed error responses that don't match the envelope schema?
When readEnvelope encounters invalid JSON or a body missing the expected structure, it throws an ApiError initialized with the provided fallback message. The responseErrorMessage utility provides additional resilience by attempting to read raw response text before falling back to the default string.
Where can I find real-world usage examples of error envelope handling?
Production implementations appear throughout src/core/persistence/ (such as cmsMedia.ts), where persistence layers call readEnvelope with domain-specific success schemas. The comprehensive test suite in src/__tests__/http/apiClient.test.ts demonstrates edge cases and validation behavior.
Is the server error envelope pattern documented outside the source code?
Yes, the architectural rationale and usage guidelines are documented in docs/reference/typebox-patterns.md under the "Server error envelope" section, which explains the design decisions behind the optional error field and TypeBox integration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →