Server API & HTTP Adapters
Relevant source files
The following files were used as context for generating this wiki page:
The Server API and HTTP Adapter system provides a standardized bridge between client-side UI components and server-side database logic. This architecture allows Better Tables to function across network boundaries, enabling true separation of frontend and backend concerns while maintaining end-to-end type safety. The system is composed of two primary pillars: the httpAdapter, which resides on the client to proxy requests, and the createAdapterRouteHandler, which mounts on the server to execute those requests against a real database adapter.
This infrastructure supports advanced features such as remote filtering, sorting, pagination, and faceted search. It also includes security seams for authorization and request constraints, ensuring that server-side resources are not blindly exposed to client-side requests.
Architecture Overview
The architecture follows a request-response pattern where the client-side adapter serializes table parameters into a standard wire protocol, which the server-side handler then deserializes and executes.
The data flow ensures that complex types like Map (used in facets) and Date objects are correctly handled across the JSON-only boundary.
Sources: plans/035-http-adapter-hardening.md:23-45, packages/core/src/adapters/http-adapter.ts:157-181
Client-Side: HTTP Adapter
The httpAdapter is a specialized implementation of the TableAdapter interface. Instead of interacting with a database directly, it sends HTTP POST requests to a specified URL. It supports standard data fetching as well as faceted values for filtering sidebars.
Configuration
The adapter is configured via an HttpAdapterConfig object, allowing for customized headers and fetch implementations.
| Option | Type | Description |
|---|---|---|
url | string | The server endpoint where the adapter handler is mounted. |
headers | HeadersInit | (() => HeadersInit | Promise<HeadersInit>) | Custom headers, useful for providing authentication tokens. |
fetch | typeof fetch | Custom fetch implementation (defaults to global fetch). |
writes | boolean | Opt-in flag to enable mutation proxying (e.g., cellEdit). |
Sources: packages/core/src/adapters/http-adapter.ts:66-106, plans/055-direct-save-path.md:240-250
Request Lifecycle
- Serialization: Parameters such as
FilterState[]orSortingStateare packaged into anAdapterRequestBody. - Transport: A
POSTrequest is sent. If the request is a facet read, anAbortSignalis attached to allow cancellation of superseded requests. - Rehydration: JSON responses are inspected. Fields like
faceted(which arrive as entry arrays) are converted back into JavaScriptMapobjects.
Sources: packages/core/src/adapters/http-adapter.ts:186-222, plans/035-http-adapter-hardening.md:158-175
Server-Side: Route Handler
The server-side is managed by createAdapterRouteHandler, which provides a standardized way to mount Better Tables adapters in frameworks like Next.js or TanStack Start.
Security and Validation
The handler implements a strict security pipeline to prevent unauthorized schema enumeration or data access.
Key Handler Options
The AdapterRouteHandlerOptions object allows developers to hook into the request lifecycle:
| Property | Description |
|---|---|
authorize | A function that returns boolean or a Response to gate access. |
constrainRequest | Allows pinning parameters like primaryTable to prevent clients from querying arbitrary tables. |
onError | A server-side sink for logging detailed error messages that should not be sent to the client. |
Sources: packages/core/src/adapters/http-handler.ts:43-60, plans/035-http-adapter-hardening.md:144-155
Direct Save & Cell Edits
While the HTTP adapter primarily handles reads, it can be configured for "Direct Save" pathing for inline cell editing. This leverages the cellEdit method in the wire protocol.
Opt-in Mutation Proxying
Proxying mutations over HTTP is a double opt-in process. Both the client httpAdapter and the server createAdapterRouteHandler must explicitly enable writes.
// apps/marketing/src/app/api/tables/tickets/route.ts
export const POST = createAdapterRouteHandler(
() => getSupportTables().then((tables) => tables.database),
{
constrainRequest: (body) =>
body.method === 'fetchData'
? { ...body, params: { ...body.params, primaryTable: 'tickets' } }
: body,
// Enable and narrow writable columns
writes: { columns: ['subject', 'status', 'priority'] }
}
);
Sources: apps/marketing/src/app/api/tables/tickets/route.ts:5-15, plans/055-direct-save-path.md:225-240
Wire Protocol Specification
The system uses a specific JSON envelope to communicate status and errors generically, protecting server internals.
Response Envelope
Responses always return an object with an ok boolean. If ok is false, a kind field specifies the error class.
bad_request: Maps to HTTP 400 or 403. Used for malformed bodies or authorization failures.server_error: Maps to HTTP 500. Used when the underlying adapter or database fails. The client receives a generic "Adapter request failed" message to prevent SQL leakage.
Data Types
- Dates: Serialized as ISO strings by the client and rehydrated by the server.
- Maps: Faceted values are sent as
[key, value][]entry arrays and reconstructed intoMap<string, number>objects on the client.
Sources: plans/035-http-adapter-hardening.md:120-134, packages/core/src/adapters/http-adapter.ts:210-220
Summary
The Server API and HTTP Adapter modules enable Better Tables to scale to complex, networked architectures. By providing a hardened http-handler that includes authorization and request constraint seams, the project ensures that powerful database adapters can be exposed safely. The http-adapter complements this by transparently handling the complexities of transport-level concerns like JSON serialization, AbortSignal propagation, and complex type rehydration, providing a seamless DX for developers building data-intensive React applications.