Skip to main content

Architecture

Hikyaku is a set of repositories that run on four platforms. Most of the difficult parts of the codebase come from this split. This page shows what runs where and how the system processes each request. A self-hosted instance needs much time, infrastructure management, and resources.

Read this page before the installation guides. It gives the reason for each setup step.

System overview​

Where the code runs​

RepositoryRuns onResponsibility
hikyakuVercelThe dashboard, the customer booking site, and all Supabase reads and writes from the browser or from server components
hikyaku-apiDocker hostRouting, optimisation, payments, card issuing, geocoding, invitations, mail, and scheduled jobs
hikyaku-mobileAndroidThe driver app. It uses one shared Kotlin Multiplatform layer for logic and UI
hikyaku-n8nn8nn8n nodes for delivery-status triggers and for package and customer lookups

Multi-tenancy: two addressing modes​

A tenant is an organisation with a slug as its identifier. There are two ways to address a tenant. You cannot use one in place of the other:

  • The dashboard uses the path. For example, /orgs/<slug>/dashboard on the application host.
  • The booking site uses the subdomain. For example, <slug>.<root-domain>/booking.

The booking site is the only content on a tenant subdomain. All other paths on a tenant host redirect to the apex domain.

If a request has a slug in the path and in the host, the path has priority. The middleware finds the active slug and sends it downstream in the x-org-slug request header. All code after the middleware reads this header. It does not parse the URL again.

Tenant requests​

This is the most complex request path in the system. It goes through a Cloudflare Worker.

Why the Worker is necessary​

The hosting platform cannot issue a certificate for a wildcard tenant host. For a DNS-01 challenge, the platform must control the nameservers of the domain. The API tunnel needs Cloudflare nameservers, so the nameservers must stay on Cloudflare. Without the Worker, Cloudflare terminates the TLS connection from the browser correctly. Then the TLS handshake to the origin fails, and users see Cloudflare error 525.

The Worker sends the request to an origin hostname that has a valid platform certificate. The Worker puts the real tenant hostname in the x-tenant-host header instead of in Host.

This design has two consequences:

  1. The shared secret is a security boundary. The origin host is available on the public internet. Without the secret, any caller can set x-tenant-host and read the booking data of a different organisation. For this reason, the middleware ignores the forwarded host if the secret does not match.
  2. Exclude the infrastructure hosts from the Worker route. The route pattern matches all subdomains, so the route explicitly excludes the application host and the API host. If you do not exclude them, the Worker adds a proxy hop in front of the API.

To move to a different hosting platform, change only the origin variable of the Worker. Cloudflare controls tenant DNS, TLS, and routing on all hosting platforms.

Data: Supabase is the system of record​

The operational database is one Supabase project. The Next.js app and the NestJS API both read from and write to this database. They connect in different ways and with different privileges:

CallerPathPrivilege
Browser and server componentsSupabase client librariesRow-level security applies
hikyaku-apiDirect Postgres connection with TypeORMService role. Row-level security does not apply
n8n nodesPostgRESTRow-level security applies

Do not put the service-role key in the web application. This key bypasses all access-control policies in the database.

Supabase owns the schema. The API uses TypeORM to connect, but the API does not manage the schema. synchronize is off, and migrations do not run at boot. Use the SQL bootstrap files in the repository as the canonical schema. Keep the generated type layer of the application aligned with these files.

Hikyaku uses these Supabase services:

  • Auth. Email and password, Google sign-in with an ID token, and WebAuthn MFA.
  • Auth OAuth Server. Hikyaku is an identity provider. This lets a tenant connect their n8n instance to their Hikyaku account without a password.
  • Postgres. The operational tables. All tables have row-level security enabled.
  • Storage. Proof of delivery, vehicle and maintenance photos, and app images. All buckets are private, except avatars.
  • Realtime. Package status changes and driver location updates.
  • Vault. Encrypted storage for third-party connection tokens.

By design, Hikyaku does not use Edge Functions, and scheduled jobs do not run in the database. The NestJS API runs all scheduled jobs. Look for cron jobs in the API.

The spatial stack​

Three containers do route planning and geocoding. They are on the same private Docker network as the API:

ServiceFunction
ValhallaRouting, distance and time matrices, elevation, and time zones
VROOMThe vehicle-routing solver. It uses Valhalla as its router
PhotonGeocoding and address search

Two security properties apply to these services:

  • They publish no host ports. Only containers on the shared network can reach them, by service name. They are not available on localhost, the LAN, or the internet.
  • They have no authentication. Any client that can reach a port can use the service. This is acceptable only because the services publish no host ports.

The API and the spatial stack are separate Compose projects. This lets you redeploy the API without a rebuild of the spatial tiles, which is slow. The first Valhalla boot takes a long time. Valhalla downloads a regional extract and builds routing tiles before it answers requests. Until Valhalla is ready, VROOM and the API return errors.

Integrations​

n8n. Hikyaku supplies a community node package. The tenant installs it in their own n8n instance. The trigger keeps one Realtime WebSocket open for each activated workflow. The number of activated workflows, not executions, counts against connection limits. Lookups go through PostgREST with the token of the user, so row-level security controls access.

Mobile. The app does not have a fixed backend at compile time. At first start, the app gets its configuration from an environment endpoint on the web app. This endpoint returns the Supabase URL, the anonymous key, and the API base URL. With this configuration, the app can connect to a self-hosted Hikyaku instance instead of the default instance.

Where to start​

  1. The sidebar component in the web app. It is the fastest map of the business domains: Packages, Customers, Driver Shifts, Fleet, Service, and Settings.
  2. The middleware. Auth refresh, tenant resolution, and the x-org-slug header all start here.
  3. The root module of the API. It has one import for each business capability, so it is also a table of contents for the backend.
  4. The SQL bootstrap files. Read the canonical schema, the roles, and the seed data, in that order.
  5. Access Control. The permission model that the database and the API enforce.

Then follow Supabase Setup to create a local environment.