Files
backend/docs/typescript-frontend-integration.md
Andriy Oblivantsev ef3957b618
CI / test (push) Successful in 4s
Demo app, collections/features CRUD, QR codes, docs
Demo app (web/):
- Collections: select, rename, remove (x button per row), delete
- Features: add point (lon/lat validation), remove, list in selected collection
- QR codes for pk (private) and pb (public) keys
- Restore public key from private key
- 409 Conflict handled for already-registered login
- Title: Momswap Geo Backend Use-Cases Test

Backend:
- PATCH /v1/collections/{id} for rename
- DELETE /v1/collections/{id}
- Clearer lon/lat validation errors (-180..180, -90..90)

Client:
- updateCollection, deleteCollection, derivePublicKey

Docs:
- docs/frontend-development.md (demo app, local dev)
- README links to all docs

Made-with: Cursor
2026-03-01 13:41:54 +00:00

4.7 KiB

TypeScript Frontend Integration Guide

This document explains how frontend developers should integrate with the backend through the reusable TypeScript client at libs/geo-api-client.

See also: Frontend Development — demo app (web/), local dev, build steps.

Primary backend URL for integration:

  • https://momswap.produktor.duckdns.org/

Deployment: API is proxied via reverse proxy from https://momswap.produktor.duckdns.org to backend at 172.17.0.1:8122. Docker Compose maps port 8122 for the reverse proxy.

Goals

  • Keep cryptographic signing logic in one place.
  • Avoid duplicating API request code in frontend apps.
  • Use a consistent local key storage format across projects.

Client package location

  • Source: libs/geo-api-client/src
  • Entry point: libs/geo-api-client/src/index.ts
  • Build output (browser ESM): libs/geo-api-client/dist/index.js

Build and test the client

cd libs/geo-api-client
bun install
bun test          # unit + integration tests (docs flow)
bun run build

Integration tests in test/integration.test.ts cover the recommended flow: register, login, create collection, create point feature, list features.

Public API (current)

Class: GeoApiClient

Constructor:

  • new GeoApiClient(baseUrl, storage, storageKey?)

Key methods:

  • ensureKeysInStorage()
  • getStoredKeys()
  • derivePublicKey(privateKey) — restore public key from private key (Ed25519)
  • importKeys(keys)
  • exportKeys()
  • setAccessToken(token)
  • getServicePublicKey()
  • createChallenge(publicKey)
  • loginWithSignature(publicKey, privateKey)
  • registerBySigningServiceKey(publicKey, privateKey) — register by signing the API service public key (no invitation required)
  • createInvitation(payload, inviterPrivateKey)
  • registerWithInvitation(...)
  • listCollections()
  • createCollection(name)
  • updateCollection(collectionId, name)
  • deleteCollection(collectionId)
  • listFeatures(collectionId)
  • createPointFeature(collectionId, lon, lat, properties)
  • deleteFeature(featureId)
  1. Create one GeoApiClient instance per backend base URL.
  2. Call ensureKeysInStorage() when app initializes.
  3. If not yet registered: call registerBySigningServiceKey(publicKey, privateKey) (signs the API service key and publishes your public key).
  4. Use loginWithSignature() to obtain and set a bearer token.
  5. Call collection/feature methods after authentication.
  6. Use importKeys/exportKeys in profile settings UX.

Registration by signing service key

When SERVICE_PUBLIC_KEY (or ADMIN_PUBLIC_KEY) is set, users can register without an invitation:

  1. GET /v1/service-key — fetch the server public key (clients use this for registration and further signed communication).
  2. Sign that key with your private key.
  3. POST /v1/auth/register-by-signature with { publicKey, signature }.

Server keys are generated with ./bin/gen-server-keys.sh and stored in etc/.

Example (TypeScript app)

import { GeoApiClient } from "../libs/geo-api-client/dist/index.js";

const storage = window.localStorage;
const storageLike = {
  getItem: (key: string) => storage.getItem(key),
  setItem: (key: string, value: string) => storage.setItem(key, value),
  removeItem: (key: string) => storage.removeItem(key),
};

const client = new GeoApiClient("https://momswap.produktor.duckdns.org", storageLike);
const keys = await client.ensureKeysInStorage();

// Register (ignored if already registered); then login
try {
  await client.registerBySigningServiceKey(keys.publicKey, keys.privateKey);
} catch (_) {
  // Already registered or registration disabled
}
await client.loginWithSignature(keys.publicKey, keys.privateKey);

const created = await client.createCollection("My Places");
await client.createPointFeature(created.id, -16.6291, 28.4636, { name: "Santa Cruz" });
const features = await client.listFeatures(created.id);
console.log(features);

Security notes

  • Private keys are currently stored in browser storage via the selected storage adapter.
  • If your frontend has stronger security requirements, wrap the storage adapter with your own encryption/decryption layer before calling setItem/getItem.
  • Never send private keys to the backend.

No-build frontend compatibility

For no-bundler apps, import the built ESM file:

<script type="module">
  import { GeoApiClient } from "../libs/geo-api-client/dist/index.js";
  // use client...
</script>

The backend itself serves static UI at /web/, but this library can be consumed by any frontend runtime that supports fetch, TextEncoder, and ES modules.

For local development you can switch the client base URL to http://localhost:8122.