App Architecture
Bkper platform apps use a three-package monorepo: a Lit + Vite client for the UI, a Hono server on Cloudflare Workers for the API, and an events package for webhook-driven automation. Mix and match only what you need.
Bkper platform apps follow a three-package monorepo pattern. Each package handles a distinct concern, all deployed to the same {appId}.bkper.app domain.
Structure
packages/├── shared/ — Shared types and utilities├── web/│ ├── client/ — Frontend UI (Vite + Lit)│ └── server/ — Backend API (Hono)└── events/ — Event handler (webhooks)The packages are connected via Bun workspaces. Import shared code from @my-app/shared in any package.
Web client
The client package builds a browser UI with Lit and @bkper/web-design for consistent Bkper styling. Authentication uses @bkper/web-auth.
- Built with Vite — fast builds and HMR during development
- Static assets served by the web server handler
- Communicates with Bkper via
bkper-js(authenticated with the web-auth SDK)
This is where your app’s UI lives — book pickers, account lists, reports, forms.
Web server
The server package runs on Cloudflare Workers using Hono as the web framework. It handles:
- Serving the client’s static assets
- Custom API routes for your app’s backend logic
- Type-safe access to platform services (KV, secrets) via
c.env
import { Hono } from 'hono';import type { Env } from '../../../../env.js';
const app = new Hono<{ Bindings: Env }>();
app.get('/api/data', async (c) => { const cached = await c.env.KV.get('my-key'); return c.json({ data: cached });});
export default app;Events handler
The events package receives webhook calls from Bkper when subscribed events occur. It’s a separate Hono app that processes events and returns responses.
import { Hono } from 'hono';import type { Env } from '../../../env.js';
const app = new Hono<{ Bindings: Env }>().basePath('/events');
app.post('/', async (c) => { const event: bkper.Event = await c.req.json();
switch (event.type) { case 'TRANSACTION_CHECKED': return c.json(await handleTransactionChecked(book, event)); default: return c.json({ result: false }); }});
export default app;Event handlers run at https://{appId}.bkper.app/events in production. During development, a Cloudflare tunnel routes events to your local machine.
See Event Handlers for patterns and details.
Shared package
Common types, utilities, and constants used across packages:
export interface EventResult { result?: string | string[] | boolean; error?: string; warning?: string;}
// packages/shared/src/constants.tsexport const APP_NAME = 'my-app';Import in any package:
import type { EventResult } from '@my-app/shared';Note: The
Envtype (KV bindings, secrets) lives in the rootenv.d.tsfile, auto-generated frombkper.yaml. Import it asimport type { Env } from '../../../env.js'— it is not part of the shared package.
When you don’t need all three
Not every app needs a UI, API, and event handler:
- Event-only app — Just the
eventspackage. Automates reactions to book events without a user interface. Remove thewebsection frombkper.yaml. - UI-only app — Just the
webpackages. Opens via a context menu to display data or collect input. Remove theeventssection frombkper.yaml. - Full app — All three packages. Interactive UI with backend logic and event-driven automation.
The template includes all three by default. Remove what you don’t need.