Overview
Start Here
Installation
Tutorial
Architect App Builder
Todo List App
Beacon Beats Audio Store
UCAL App
Authoring
Dynamic Apps
Static Apps
dev_docs
Why Pharos Feels Different
Architect Builder
Framework
Artifacts & Layout
Configuration
Outbound Email
How to Use CLI
Contracts
YAML Authoring
Action Hooks and Middleware
Feature Transaction Primitives
Generated Surfaces
Declarative CRM
Input Validation
Identity and Tenancy
UI Surface Library
Social Connections
Payment Provider Management
Integration Adapters
Reference
Contract, UI, and Surface Reference
Styling UI Elements
Architect Media Artifacts
API Docs
Use Cases
Dynamic SPAs
Generated Admin and APIs
Scheduling and Marketplaces
Multi-App Hosting
Release Notes
Vision
Pharos Framework

← Back

Pharos: Build the App, Not the Bridge

Most dynamic web stacks make you build the same feature twice: once as the product the user sees, then again as transport code joining browser state to server state.

For Beacon Beats’ Add to cart button, that second implementation would often mean an API controller, request and response models, browser fetch, a client cart store, submit/loading/error state, router code, cache invalidation, and repeated authentication and CSRF wiring.

Pharos lets the application declare the interaction instead.

The browser submits intent

The product card is a normal form:

<form pharos-action="add_pack" pharos-target="cart-panel">
  <input type="hidden" name="product_id" value="{id}">
  <button class="button button-primary" type="submit">Add pack to cart</button>
</form>

It submits product identity—not price, currency, user, owner, total, or asset path. At build time Pharos resolves add_pack to the declared method and route, adds the CSRF field, and verifies that cart-panel is a declared response target.

The feature says what the action means

The cart feature reads the published product and current user’s cart, performs an idempotent relational upsert, then refreshes and renders the cart region:

actions:
  add_pack:
    path: /cart/items
    method: POST
    policy: customer
    input:
      product_id: {type: integer, required: true, minimum: 1}
    transaction:
      steps:
      - require: {query: published_product}
      - require: {query: current_cart}
      - upsert:
          entity: shopping_cart_item
          unique_by: [cart_id, product_id]
          values:
            cart_id: query.current_cart.id
            user_id: subject.user_id
            product_id: query.published_product.id
            price_minor: query.published_product.price_minor
            currency: query.published_product.currency
    response:
      render: cart_summary
      target: cart-panel
      queries: [current_cart_items]
      fallback: {redirect: /cart}

The returned fragment is ordinary app-owned HTML. Pharos runs the authoritative query after commit and renders its values into that fragment.

What happens on Add to cart

  1. Pharos matches the declared action and applies session, origin, CSRF, policy,
  2. body-size, and input gates.

  3. It binds product_id into parameterized queries; browser values are never
  4. interpolated into SQL.

  5. It re-reads publish state, price, currency, and the current user’s cart.
  6. It commits the declared upsert.
  7. It re-runs current_cart_items and renders cart_summary.
  8. The framework interaction client replaces cart-panel, restores focus, and
  9. announces the update. A browser without enhancement follows /cart instead.

Open Network while using Beacon Beats. The mutation is one form POST returning HTML for the declared region. There is no app-written /api/cart/items, JSON serializer, client cart model, submit handler, router call, or refetch.

The mental model changes from “my frontend calls my backend” to “my app has a page, a form, a transaction, and a committed-state fragment.” The HTTP boundary still exists; Pharos owns the repetitive mechanics so it does not become a second application architecture you maintain.

What you can leave behind

Pharos reduces the amount of stack assembly each app has to carry. For many application surfaces, you no longer need to maintain:

  • frontend frameworks just to get admin and API surfaces
  • separate language runtimes for app serving
  • application servers such as Gunicorn, uWSGI, or Passenger
  • heavy ORM layers and database abstraction libraries
  • frontend build chains such as webpack, Vite, or Babel
  • dedicated email libraries for framework-level SMTP delivery

The result is a smaller application boundary: the Pharos runtime, a configured database backend when the app needs persistence, and a reverse proxy such as Nginx or Caddy for TLS and domain routing.

It still scales to real application work

Beacon Beats applies the same model to public catalog queries, URL filters, server-driven navigation, cart removal, atomic order and entitlement creation, My Library, protected range-capable downloads, and an owner dashboard. The only app-authored JavaScript controls audio playback because that behavior genuinely belongs in the browser.

Pharos does not make your domain decisions disappear. You still own entities, business rules, policies, templates, brand, assets, and deliberate integration APIs. Pharos owns the reusable request, validation, persistence, rendering, security, media, packaging, and operational machinery surrounding those decisions.

That makes the same approach useful for publishing, SaaS dashboards, booking, marketplaces, customer portals, internal tools, and other dynamic SPAs—not only commerce.

Next, continue into Build a Scheduling and Workflow App and the broader framework reference.