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.

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:

  1. Serialization: The tRPC client serializes the procedure name and input parameters.
  2. Batching: If multiple calls occur simultaneously, httpBatchLink bundles them into one HTTP POST request to /api/trpc.
  3. Server Handling: Hono receives the request and forwards it to the tRPC handler, which routes to the appropriate procedure in appRouter.
  4. Execution: The procedure executes the domain logic (e.g., querying the database for bookmarks).
  5. 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

Summary

  • tRPC on Hono: The backend exposes a type-safe API via an appRouter mounted at /api/trpc using Hono as the HTTP server.
  • Batched Client: The frontend uses createTRPCClient with httpBatchLink to 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.ts and 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →