How the Kaneo Web App Interacts with the Kaneo API: Architecture Deep Dive
The Kaneo web application communicates with its backend API through a type-safe Hono client that auto-generates TypeScript definitions from the OpenAPI schema, wrapped by specialized fetcher utilities and TanStack Query hooks for state management, with real-time updates delivered via WebSocket connections.
The Kaneo project management platform implements a fully-typed communication layer between its React frontend and Hono-based backend to eliminate runtime contract mismatches. Understanding exactly how the kaneo web app interacts with the kaneo api reveals a sophisticated pattern of compile-time validation, centralized request configuration, and efficient data synchronization. This architecture lives in the usekaneo/kaneo monorepo, where shared TypeScript definitions ensure the frontend and backend remain in perfect alignment.
The Type-Safe Hono Client
At the core of the communication layer sits the Hono client, generated from the shared OpenAPI contract (AppType). Located in packages/libs/src/hono.ts, this client instance wraps the native fetch API and automatically injects required headers while handling cookie-based authentication.
// packages/libs/src/hono.ts
export const client = hc<AppType>(apiUrl, {
headers: {
"Content-Type": "application/json",
"X-Kaneo-Window-Id": windowId,
},
credentials: "include", // Enables cookie-based auth
});
The client configuration ensures every request includes the Content-Type: application/json and X-Kaneo-Window-Id headers, while credentials: "include" preserves authentication cookies across cross-origin requests. This singleton pattern guarantees consistent request formatting throughout the application.
Dynamic API URL Construction
For endpoints not covered by the Hono client—such as WebSocket connections or custom routes—the frontend uses a dedicated helper function. The getApiUrl utility in apps/web/src/fetchers/get-api-url.ts normalizes base URLs and constructs absolute paths.
// apps/web/src/fetchers/get-api-url.ts
export function getApiUrl(path: string) {
const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000";
return `${baseUrl}/${path.replace(/^\//, "")}`;
}
This utility is essential for environment-specific routing, allowing the same codebase to target development, staging, and production APIs without hardcoded URLs.
Fetcher Functions and API Operations
Each API operation is encapsulated in a thin async fetcher function that either invokes the typed Hono client or uses native fetch with getApiUrl. These functions handle request serialization, response parsing, and error normalization.
// apps/web/src/fetchers/project/create-project.ts
export async function createProject({ name, slug, workspaceId }: CreateProjectInput) {
const response = await client.project.$post({
json: { name, slug, workspaceId },
});
if (!response.ok) {
throw new Error("Failed to create project");
}
return response.json();
}
Similar fetchers exist for tasks, workspaces, and user operations, creating a predictable interface where components never call the API directly. This abstraction layer simplifies testing and allows for centralized error handling and request logging.
React Query Integration for State Management
The UI layer consumes these fetchers through TanStack Query hooks located under apps/web/src/hooks/queries/. These hooks provide caching, background refetching, and loading state management while maintaining type safety through the shared schema.
// apps/web/src/hooks/queries/project/use-get-project.ts
import { useQuery } from "@tanstack/react-query";
import { getProject } from "@/fetchers/project/get-project";
export function useGetProject({ id, workspaceId }: GetProjectInput) {
return useQuery({
queryKey: ["projects", workspaceId, id],
queryFn: () => getProject({ id, workspaceId }),
enabled: Boolean(id && workspaceId),
});
}
This pattern decouples data fetching from component logic, ensuring that multiple components referencing the same project share a single cached instance. The queryKey structure enables automatic cache invalidation when workspace or project identifiers change.
Real-Time Updates via WebSocket
For live notifications and collaborative features, Kaneo establishes WebSocket connections alongside its REST API. The use-user-websocket.ts and use-project-websocket.ts hooks manage these persistent connections, using getApiUrl("ws") to resolve the WebSocket endpoint.
// apps/web/src/hooks/use-user-websocket.ts
export function useUserWebsocket() {
const [socket, setSocket] = useState<WebSocket | null>(null);
useEffect(() => {
const wsUrl = getApiUrl("ws").replace(/^http/, "ws");
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log("WebSocket connected");
};
setSocket(ws);
return () => ws.close();
}, []);
return socket;
}
On the backend, message broadcasting is handled by adapter classes in apps/api/src/ws/redis-broadcast-adapter.ts and apps/api/src/ws/in-memory-broadcast-adapter.ts, enabling horizontal scaling through Redis or single-node deployments using memory-based pub/sub.
Summary
The Kaneo web app interacts with its API through a layered architecture that prioritizes type safety and developer experience:
- Hono client (
packages/libs/src/hono.ts) provides compile-time type checking via theAppTypeOpenAPI schema - Fetcher utilities abstract HTTP requests and response handling for all CRUD operations
- TanStack Query hooks manage server state, caching, and synchronization across components
- WebSocket connections deliver real-time updates through the same URL resolution logic as REST endpoints
- Cookie-based authentication (
credentials: "include") maintains secure sessions without manual token management
This stack ensures that changes to the backend API surface immediately trigger TypeScript errors in the frontend, preventing deployment of mismatched contracts.
Frequently Asked Questions
How does Kaneo ensure type safety between the frontend and backend?
Kaneo generates a shared AppType TypeScript definition from the backend's OpenAPI specification. The Hono client in packages/libs/src/hono.ts uses this type to enforce correct request payloads and response shapes at compile time, eliminating the need for runtime validation libraries and preventing API contract drift.
What authentication mechanism does the Kaneo web app use for API requests?
The application relies on cookie-based session authentication configured via credentials: "include" in the Hono client initialization. This approach stores HTTP-only session cookies that are automatically transmitted with each request, eliminating the need for manual JWT handling in the frontend code while protecting against XSS attacks.
How does Kaneo handle real-time collaborative updates?
Real-time functionality is implemented through WebSocket connections established by hooks like use-user-websocket.ts and use-project-websocket.ts. These connections target the /ws endpoint resolved through getApiUrl, while the backend uses broadcast adapters (apps/api/src/ws/redis-broadcast-adapter.ts) to distribute messages across all connected clients in a workspace.
What is the purpose of the X-Kaneo-Window-Id header?
The X-Kaneo-Window-Id header identifies specific browser tab instances to the backend, enabling the API to track and manage WebSocket connections per client window rather than per user. This granularity allows the server to send targeted updates to specific tabs and manage connection lifecycle events accurately during multi-tab usage.
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 →