Skip to content

Your First App

Step-by-step walkthrough of building and deploying a full Bkper platform app from scratch — scaffold from template, run locally with HMR and automatic webhook tunnels, react to transaction events, and go live with a single deploy command.

This tutorial walks you through building and deploying a Bkper app from scratch. You’ll use the Bkper Platform — managed hosting, pre-configured OAuth, and automatic dev tunnels — so you can focus entirely on business logic.

By the end, you’ll have a working app live at https://my-app.bkper.app that displays account balances and reacts to transaction events.

Prerequisites

Make sure you’ve completed Development Setup — the CLI installed and authenticated.

Walkthrough

  1. Create from the template

    Terminal window
    bkper app init my-app
    cd my-app

    This scaffolds a new project from the official template. The structure you get:

    my-app/
    ├── bkper.yaml ← Your entire infrastructure config
    ├── env.d.ts ← Auto-generated type definitions
    ├── .dev.vars.example ← Template for local secrets
    └── packages/
    ├── shared/ ← Shared types and utilities
    ├── web/
    │ ├── client/ ← Frontend UI (Lit + Vite)
    │ └── server/ ← Backend API (Hono on Workers)
    └── events/ ← Event handlers (webhooks)
  2. Look at bkper.yaml

    Open bkper.yaml — this single file is your entire infrastructure configuration:

    id: my-app
    name: My App
    description: A Bkper app that does something useful
    # Context menu entry in Bkper
    menuUrl: https://my-app.bkper.app?bookId=${book.id}
    menuUrlDev: http://localhost:8787?bookId=${book.id}
    # Event subscriptions
    webhookUrl: https://my-app.bkper.app/events
    events:
    - TRANSACTION_CHECKED
    # Deployment config
    deployment:
    web:
    main: packages/web/server/src/index.ts
    client: packages/web/client
    events:
    main: packages/events/src/index.ts
    services:
    - KV
    secrets:
    - BKPER_API_KEY

    This declares hosting, OAuth integration, event subscriptions, KV storage, and secrets — no cloud console required.

    See App Configuration for the full bkper.yaml reference.

  3. Start developing

    Terminal window
    bkper app dev

    A single command starts everything:

    • Vite dev server — hot module replacement for your UI
    • Miniflare — simulates the Cloudflare Workers runtime locally
    • Cloudflare tunnel — exposes your events handler at a public URL so Bkper can send webhooks to your laptop

    You’ll see output like:

    [web] Local: http://localhost:8787
    [events] Tunnel: https://a1b2c3.trycloudflare.com/events
  4. See the running app

    Open http://localhost:8787.

    You’re greeted with a book picker — no login screen, no redirect, no auth setup. The platform handled OAuth automatically. Select a book to see account balances.

    The client code that makes this work is about 30 lines in packages/web/client/src/components/my-app.ts:

    private auth = new BkperAuth({
    onLoginSuccess: () => this.loadData(),
    onLoginRequired: () => this.auth.login(),
    });
    private async loadData() {
    this.bkper = new Bkper({
    oauthTokenProvider: async () => this.auth.getAccessToken(),
    });
    this.user = await this.bkper.getUser();
    this.books = await this.bkper.getBooks();
    }

    That’s all the auth code you write. The platform provides the token endpoint, consent screen, and refresh handling.

  5. Trigger an event

    Go to your Bkper book and check (reconcile) any transaction.

    Back in your terminal, you’ll see the event handler fire. The template handler in packages/events/src/handlers/transaction-checked.ts creates a 20% draft transaction:

    export async function handleTransactionChecked(
    book: Book,
    event: bkper.Event
    ): Promise<EventResult> {
    const operation = event.data?.object as bkper.TransactionOperation;
    const tx = operation.transaction;
    // Prevent loops — skip transactions created by this app
    if (event.agent?.id === 'my-app') return { result: false };
    const newAmount = Number(tx.amount) * 0.2;
    const draft = new Transaction(book)
    .setDate(tx.date)
    .setAmount(newAmount)
    .setDescription(`20% of ${tx.description}`)
    .setCreditAccount(tx.creditAccount)
    .setDebitAccount(tx.debitAccount);
    await draft.create();
    return { result: `Created draft: 20% of ${tx.description}` };
    }

    The event arrived via the Cloudflare tunnel the CLI started — no manual ngrok setup, no URL configuration.

  6. Make a change

    Edit the handler to use 10% instead of 20%:

    const newAmount = Number(tx.amount) * 0.1; // changed from 0.2

    Save the file. The Workers runtime reloads automatically. Check another transaction — you’ll see the new 10% amount.

  7. Deploy to production

    Terminal window
    bkper app deploy

    The CLI builds all three packages, syncs your app metadata to Bkper, and deploys to the platform:

    ✓ Built web client
    ✓ Built web server
    ✓ Built events handler
    ✓ Deployed to https://my-app.bkper.app

    Your app is now live. Bkper will route production webhook events to https://my-app.bkper.app/events.

What just happened

You built and deployed a full Bkper app. Here’s what you wrote versus what the platform handled:

You wrotePlatform handled
~30 lines: book picker + account list UIOAuth client registration and consent screen
~40 lines: event handler (business logic)Token endpoint and refresh
bkper.yaml: infrastructure as configGlobal edge hosting (my-app.bkper.app)
Webhook routing and delivery
Dev tunnel (webhooks on localhost)
KV storage provisioning
SSL certificates
env.d.ts type generation

The platform handles the infrastructure. You handle the logic.

Next steps