How Does the Karakeep Backend Communicate with the Frontend?
Karakeep uses a type-safe tRPC-based HTTP API running on a Hono server, where the backend exposes domain logic through a centralized appRouter mounted at /api/trpc, and the Next.js frontend consumes these procedures using a batched tRPC client integrated with React Query.
Karakeep is an open-source bookmarking application that prioritizes developer experience through end-to-end type safety. Understanding how the Karakeep backend communicates with the frontend reveals a modern architecture built on tRPC, Hono, and React Query that eliminates API contract mismatches and reduces network overhead through automatic request batching.
Backend Architecture: The tRPC Router and Hono Server
The backend exposes all domain logic through a single tRPC router that aggregates feature-specific sub-routers.
The Centralized appRouter
In packages/trpc/routers/_app.ts, the application defines a root router that composes all feature modules:
import { router } from "../index";
// ... other imports
export const appRouter = router({
bookmarks: bookmarksAppRouter,
apiKeys: apiKeysAppRouter,
users: usersAppRouter,
// ... other feature routers
});
export type AppRouter = typeof appRouter;
This appRouter serves as the single entry point for all API calls. The export of AppRouter type is critical—it provides the TypeScript definition that the frontend uses to guarantee procedure names and input/output types match exactly between client and server.
Hono Server Integration
The tRPC router is mounted on a Hono HTTP server (see apps/api/src/server.ts), creating the actual HTTP endpoint at /api/trpc. Hono handles the underlying HTTP layer including middleware, while tRPC manages request validation, type-safe routing, and error handling. When requests hit /api/trpc/*, Hono forwards them to the tRPC handler which dispatches to the appropriate procedure in the appRouter.
Frontend Integration: Next.js and React Query
The frontend consumes the backend API through a tRPC client configured with performance optimizations.
Client Initialization with httpBatchLink
The frontend creates a tRPC client in packages/sdk/src/trpc.ts using httpBatchLink to enable request batching:
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import type { AppRouter } from "@karakeep/trpc/routers/_app";
export const trpc = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: "/api/trpc", // Relative path works in production
}),
],
});
This configuration, also illustrated in tools/seed-snapshot/src/index.ts, allows multiple simultaneous procedure calls (like bookmarks.list and users.me) to be bundled into a single HTTP request, significantly reducing network chatter.
React Query Integration
The client is wired into TanStack React Query (see packages/sdk/src/react-query.ts) for caching and automatic refetching. Components call procedures using standard React Query hooks:
import { useQuery } from "@tanstack/react-query";
import { trpc } from "@/trpc";
export const BookmarkList = () => {
const { data, isLoading } = useQuery(
["bookmarks"],
() => trpc.bookmarks.list.query()
);
if (isLoading) return <p>Loading…</p>;
return (
<ul>
{data?.map(b => (
<li key={b.id}>{b.title}</li>
))}
</ul>
);
};
Data Flow and Type Safety
When a component invokes a procedure like trpc.bookmarks.list.query(), the following sequence occurs:
- Serialization: The tRPC client serializes the procedure name and input parameters.
- Batching: If multiple calls occur simultaneously,
httpBatchLinkbundles them into one HTTP POST request to/api/trpc. - Server Handling: Hono receives the request and forwards it to the tRPC handler, which routes to the appropriate procedure in
appRouter. - Execution: The procedure executes the domain logic (e.g., querying the database for bookmarks).
- Response: Results return as JSON, React Query updates its cache, and the UI re-renders with fresh data.
This architecture provides end-to-end type safety because the same TypeScript types define both the server procedures and client calls, eliminating the possibility of API drift or contract mismatches.
Key Implementation Files
packages/trpc/routers/_app.ts: Defines the centralappRouterthat aggregates all feature routers (bookmarks, users, apiKeys).packages/sdk/src/trpc.ts: Exports the configured tRPC client used throughout the web application.packages/sdk/src/react-query.ts: Bridges tRPC calls with TanStack React Query for caching and state management.apps/api/src/server.ts: Mounts the tRPC router on the Hono server at the/api/trpcendpoint.tools/seed-snapshot/src/index.ts: Reference implementation showing client setup withhttpBatchLink.
Summary
- tRPC on Hono: The backend exposes a type-safe API via an
appRoutermounted at/api/trpcusing Hono as the HTTP server. - Batched Client: The frontend uses
createTRPCClientwithhttpBatchLinkto bundle multiple requests into single HTTP calls for efficiency. - React Query Bridge: tRPC integrates with TanStack React Query (
packages/sdk/src/react-query.ts) to provide caching, background refetching, and optimistic updates. - Zero-Contract Risks: Shared TypeScript types between
packages/trpc/routers/_app.tsand the frontend SDK ensure compile-time guarantees for API inputs and outputs.
Frequently Asked Questions
What protocol does Karakeep use for frontend-backend communication?
Karakeep uses HTTP with tRPC as the application protocol. While the transport is standard HTTP POST requests to /api/trpc, tRPC handles the RPC layer, providing type-safe procedure calls over the wire instead of traditional REST endpoints.
How does Karakeep maintain type safety across the API boundary?
Type safety is enforced by importing the AppRouter type from packages/trpc/routers/_app.ts into the frontend client initialization. This single source of truth ensures that if a procedure name changes or an input schema is modified on the backend, TypeScript compilation on the frontend will fail immediately, preventing runtime errors.
Why does Karakeep use request batching for API calls?
The frontend configures httpBatchLink to automatically batch multiple simultaneous tRPC procedure calls into a single HTTP request. This reduces network overhead and improves performance when components make several data requests at once, such as during initial page loads.
Where is the tRPC router defined in the Karakeep codebase?
The main router is defined in packages/trpc/routers/_app.ts as the appRouter constant. This file imports and combines sub-routers (like bookmarksAppRouter, usersAppRouter) into a single unified router that represents the entire backend API surface.
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 →