Skip to main content
This page explains how a Voyant application is put together. Read it once before you start building and the rest of the documentation will make more sense.

The big idea

Voyant is a set of headless domain modules that you compose into an application. Each module owns one slice of the travel business, such as catalog, bookings, or finance. A module brings its own database schema, services, and HTTP routes, plus an optional React package for its UI. You pick the modules you need, wire them together with links, and deploy the result as a single application per organization. The platform keeps a strict boundary:
  • Packages hold reusable business logic, schemas, services, routes, and adapters. They are framework-agnostic.
  • Starters and app shells own UI, auth wiring, deployment shape, and runtime configuration.
This means the same domain logic runs whether you deploy to your own infrastructure or to Voyant, and whether your UI is the first-party React shell or something you build yourself.

The stack

Voyant runs as a resident Node process against Postgres. The deployable is a container image: Core packages do not depend on any of these choices. They expose plain services and an HTTP contract, so you can host them differently if you need to.

Modules

A module is a package such as @voyant-travel/bookings or @voyant-travel/catalog. Each one is self-contained:
  • A Drizzle schema for its tables.
  • Domain services that enforce the rules of that subdomain.
  • HTTP routes exposing those services over the API.
  • A matching -react package (for example @voyant-travel/bookings-react) with hooks, clients, query keys, and reusable UI.
  • A contracts package (for example @voyant-travel/bookings-contracts) describing the wire types shared between server and client.
You add a module by installing it and registering its schema and routes, or you generate a brand new one with the CLI. See Modules for the full catalog and the generator. Modules stay decoupled, but real travel data is relational: a person belongs to organizations, a booking references a quote, a product references a place. Voyant models these cross-module relationships with links. A link is a declarative relationship between an entity in one module and an entity in another. You declare it once and the platform can emit the link table DDL for you:
This keeps foreign-key style relationships explicit and discoverable without forcing every module to import every other module. The voyant db sync-links command emits the SQL or a generated Drizzle schema for all declared links.

Transport and contracts

Every module exposes its services over HTTP using Hono. The wire shape of those routes lives in a dedicated -contracts package, so the server implementation and the client SDK share one source of truth for request and response types. The -react packages consume those contracts to give you typed hooks instead of hand-written fetch calls. This contract-first approach is the same idea behind the public SDKs: a typed client generated against a stable API surface.

Jobs and subscribers

Subscribers react to domain events. Package-owned jobs handle scheduled or wakeable background work and call the same domain services as routes and subscribers. The host supplies cadence, leases, retries, and health while durable domain records remain authoritative. Selecting a package in voyant.config.ts selects its closed job inventory. See Jobs.

Surfaces: storefront and admin

Two surfaces sit on top of the modules, and they are not the same shape:
  • The Public API is the customer-facing HTTP contract under /v1/public: discovery, product detail, checkout, and self-service. The platform owns the contract; you own the frontend that consumes it, whether that is a Theme, the managed booking engine, or something you host yourself.
  • The admin is the staff-facing operations console: managing quotes, bookings, inventory, finance, and the rest of the back office. It ships as a ready-made React surface assembled from the domain modules and their React packages.
Both are served by the same domain modules. See Public API and Admin.

The deployment and security boundary

Voyant’s tenancy model is deliberately simple: one Postgres database and one runtime per organization. Isolation is enforced at the deployment boundary, not by in-process middleware that filters rows by tenant id. This is a foundational decision. It removes a whole class of cross-tenant data-leak bugs, keeps queries simple, and makes each customer’s data physically separate. When you deploy to Voyant, the platform provisions and manages that per-organization runtime and database for you.

Where to go next

Glossary

The shared travel vocabulary every module uses.

Project structure

What a scaffolded Voyant project actually contains.

Module catalog

Every domain module and what it owns.

Jobs

Understand package-owned background work.