Elements vs Firebase

Markdown

Firebase is Google's backend-as-a-service for mobile and web apps. It hosts the backend for you as a set of services under one project: the Cloud Firestore document database, Firebase Authentication, Cloud Functions for server code, Cloud Storage for files, and Hosting. Apps call these services directly from the browser or phone through Firebase's client SDKs, and Security Rules decide what each request may read or write. Firebase bills by usage: past the free Spark plan's limits, the Blaze plan bills by document reads and writes, function invocations, storage and bandwidth (pricing).

Elements is not a backend-as-a-service. It is an integrated app environment in which the backend is part of your app instead of a service it calls. Where Firebase rents you a database, sign-in, functions and hosting one service at a time, Elements ships their counterparts as one coherent system: a bundled Postgres database, sessions and sign-in, realtime data with LiveTable and channels, typed server functions with @rpc, background jobs and email, along with the test runner that checks them and the deploy command that ships them. The app goes to an Ubuntu server you rent from anyone and reach over SSH, and Elements is paid for per machine, with no read, write or invocation ever counted (pricing).

The difference is where those services live. On Firebase, each one is a separate Google service your app calls over the network, secured by rules written in Firebase's rules language and billed by the operation. In Elements, they are part of your app. They are written in TypeScript and SQL, checked by the same build as the rest of your code, keep their data in the Postgres your app already uses, and deploy with the app in one command to a server you choose.

At a Glance

Elements Firebase
What it is Your backend inside your app: project server, default framework, Postgres and deploy as one coherent system A set of hosted backend services from Google
Database Postgres, queried with SQL Firestore documents, or Postgres through GraphQL with SQL Connect
Who can read the data Only the server functions you write Any client that passes your Security Rules
Realtime LiveTable and channels Firestore listeners, each change billed as a document read
Server code @rpc functions and routes in your app Cloud Functions, deployed separately, on the Blaze plan
Sign-in and sessions Built in Firebase Authentication
Background jobs Built in, with retries and cron Scheduled and event-triggered Cloud Functions
Local development Your app running on the real Postgres, with each save checked in milliseconds by the project server An emulator for each service
What you pay for Tooling, per machine (pricing) Reads, writes, invocations, storage and bandwidth
Where it runs Your own Ubuntu machine, from any provider Google Cloud
Growing Add machines behind the built-in load balancer, sharing one Postgres Usage grows the bill for each service
Leaving pg_dump your Postgres; the code is TypeScript, HTML, CSS and SQL Replace Firestore, Authentication, Security Rules and Cloud Functions

Your App Has a Server, So Your Data Needs No Rules Language

Firebase is built for apps whose clients talk to the database directly. A browser or phone reads and writes Firestore through the client SDK, so every request has to be checked where it lands: "Every database request from a Cloud Firestore mobile/web client library is evaluated against your security rules before reading or writing any data" (Security Rules). The rules are a separate language, kept in a separate file, and a mistake in them shows up when a request is allowed or refused, not when the code is built.

An Elements app has its own server, so the browser never touches a table. It reaches data only through an @rpc function or a LiveTable handler you wrote, and the authorization check is the first line of that function, in TypeScript, where anyone reading the code sees it (rpc, LiveTable handlers). The browser calls an @rpc like a TypeScript function, so the build checks every argument. A slip in the other direction is caught as well: if browser code calls sql(), tx() or session.login(), the build fails on that line. And because authorization is ordinary code, the test runner inside the build tests it against the real Postgres (tests).

import { session, sql, ForbiddenError } from "@elements/app";

/** @rpc */
export function deletePost(postId: string) {
  session.isLoggedInOrThrow();

  let post = sql<{ authorId: string }>(`
    select authorId from posts where id = ${postId}
  `).firstOrThrow("post not found");

  if (post.authorId !== session.getOrThrow("userId")) {
    throw new ForbiddenError();
  }

  sql(`delete from posts where id = ${postId}`);
}

Postgres, Queried With Plain SQL

Firestore stores documents in collections, and an app queries them through Firebase's SDKs (Firestore). For apps that need relational data, Firebase added SQL Connect, backed by Cloud SQL for PostgreSQL. Its schema and operations are written in GraphQL: "Your schema is the app data model for a service, represented primarily as a collection of GraphQL source files" (SQL Connect).

Elements gives you Postgres with SQL itself, which coding agents already write. Queries are SQL written in TypeScript with the sql() function; at compile time the build makes each ${value} a bound parameter, which is why interpolation cannot open an injection hole. Joins, aggregates and transactions are Postgres's own. Where SQL Connect has you edit a GraphQL schema, Elements has you write a migration, a SQL file the project server applies as soon as you save it. Elements sets up the database for you, on your machine and on the servers you deploy to, and it connects to any managed Postgres through DB_HOST (database).

Realtime Is Built In and Never Billed

Firestore's realtime listeners push changes to every client watching a query, and each change is billed: "you are charged for a read each time a document in the result set is added or updated" (Firestore billing).

Elements includes realtime in its default framework. Where a Firestore listener streams documents, a LiveTable streams Postgres rows: it arrives already filled in the server-rendered page, shows a user's own edit immediately while the server writes it, and patches only the changed row in every other browser watching. For anything that is not a row, channels push messages from the server to whichever browsers subscribe. Both run on Postgres LISTEN/NOTIFY, with no extra service to deploy, and live updates never appear on an Elements bill.

Server Code Ships With the App

Firebase runs server code as Cloud Functions, "a serverless framework that lets you automatically run backend code in response to events triggered by background events, HTTPS requests, the Admin SDK, or Cloud Scheduler jobs" (Cloud Functions). Deploying one takes the paid plan: "to deploy functions, your project must be on the Blaze pricing plan" (get started). Pages are served separately: Firebase Hosting serves static and single-page apps and is "paired with Cloud Functions or Cloud Run" for dynamic content, and App Hosting serves server-rendered Angular and Next.js apps (Hosting).

In Elements, server code is part of the app. Pages, @rpc functions, route handlers, background jobs and cron, and the code that sends email through any SMTP provider run in the same system, reading the same session and the same database. Jobs live in a queue in your Postgres, so a job scheduled inside a transaction commits with the rest of the write or not at all.

One elements deploy ships all of it to any Ubuntu server you reach over SSH. That replaces separate Hosting and Cloud Functions deploys with one step, taking a few seconds the first time on a new machine, often under a second after that, since only changed files travel. Each machine runs the tests, the first one runs migrations, and a failure at any step leaves the current release serving (what a deploy does). One server runs the app, the load balancer, which issues and renews TLS certificates, and Postgres, a good fit for a prototype or an app on one server. To grow past that, or to get managed backups and scaling, add machines behind the built-in load balancer and connect them to a hosted Postgres, such as DigitalOcean Managed PostgreSQL or Amazon RDS, by setting DB_HOST (load balancer).

Sign-In and Sessions Are Built In

Firebase Authentication is a hosted service with its own SDKs and user store. Past a free allowance of monthly active users it bills at Google Cloud Identity Platform rates, and SMS sign-in is billed per message (Authentication, pricing).

In Elements, sessions are part of the framework. One API works in route handlers, @rpc functions and templates, over HTTP and over WebSockets, and a template that reads the session updates the moment a user signs in, without a reload. The manual's authentication recipe gives the schema and handlers for sign-up and sign-in, with passwords stored as bcrypt hashes in your own users table. Your users are rows in your database, and signing them in costs nothing per user.

Your Backend Is Checked in Milliseconds, on Your Machine

An agent fixes a mistake fastest when the build reports it. A Firebase feature touches the client SDK, the Security Rules language and often a Cloud Function, each with its own API to look up, and no build checks the client's queries against the rules. An SDK saves an agent time only for what it cannot write quickly from standards. Reading and writing an app's own data is not on that list: it is SQL and TypeScript, which the agent already knows.

Developing a Firebase app locally means running emulators. The Local Emulator Suite imitates Firestore, Authentication, Storage, Hosting and Cloud Functions on your machine (Emulator Suite), so what you test locally is a stand-in for the services your app will call in production.

Elements needs no emulators: your app runs on your machine against the real Postgres. The data access, the permission check and the server code are ordinary functions in one TypeScript project, and since Elements is one coherent system, one build checks all of them together. In place of the emulators, a project server holds the build graph and build state in memory, so on every save the build state is updated almost instantly. It builds in milliseconds and answers agents and humans in microseconds (build). One elements build -json answer covers every build error, from test results and migration errors to the findings of type checking and program analysis across code and templates. Tests run the same code you deploy, against real Postgres, each inside a transaction that rolls back when it ends (tests).

Nothing in Elements Is Metered

On the Blaze plan, Firebase bills each service by use: Firestore by document read, write and delete, Cloud Functions by invocation and compute time, and Hosting, Storage and Authentication by their own measures, past the free quotas (pricing). A budget does not stop the spending: "Budget alerts do not pause services," and Firebase lists Cloud Functions "that cause excessive fan-out workloads or even infinite loops" among the causes of surprise bills (avoid surprise bills).

Elements is licensed per machine, on a subscription (licenses). Nothing is metered: reads, writes, live updates, function calls, sign-ins and bandwidth never appear on an Elements bill. Your server and your database cost what your provider publishes.

Your Data Stays in Standard Postgres

A Firebase app is written against Google's SDKs. Its data sits in Firestore documents, its users in Firebase Authentication, its access rules in Security Rules, and its server code in Cloud Functions, so moving to another platform means replacing each of them.

Nothing in an Elements app is written against a proprietary SDK: it is TypeScript, HTML, CSS and SQL, running on Node.js against standard Postgres. The data, including your users, sits in ordinary tables that pg_dump, psql and any Postgres host understand. Moving it means setting DB_HOST to another database's address, or renting another server and running elements deploy (deploy setup). The code your app imports at runtime, @elements/app among it, is under the MIT license (licenses).

Questions

What is Firebase?

Firebase is Google's backend-as-a-service for mobile and web apps: hosted services including the Firestore database, Authentication, Cloud Functions, Cloud Storage and Hosting. Client SDKs call the services directly from the app, Security Rules decide what each request may read or write, and the Blaze plan bills by usage.

Is Elements a Firebase alternative?

Yes. Elements ships sessions and sign-in, realtime data with LiveTable and channels, typed server functions with @rpc, file uploads, background jobs and email inside your own app, on a bundled Postgres database, and deploys the app to your own server.

Does Elements use a NoSQL database like Firestore?

No. Elements bundles Postgres and you query it with SQL through its sql() function, with joins, aggregates and transactions from Postgres itself. Schema changes are SQL migration files that the project server applies when you save them.

Does Elements need security rules like Firebase?

No. The browser never reads a table directly; it goes through @rpc functions and LiveTable handlers you write, with the authorization check at the top in TypeScript. Browser code that calls sql() does not compile.

How much does Elements cost compared to Firebase?

Firebase's Blaze plan bills by use: document reads and writes, function invocations, storage and bandwidth. Its budget alerts do not pause services (Firebase pricing). Elements is licensed per machine, with nothing metered (Elements pricing).

Where does an Elements app run?

On any Ubuntu server you reach over SSH, from any provider, rather than on Google Cloud. One server runs the app, the load balancer, which issues and renews TLS certificates, and Postgres. To grow, add machines: the load balancer spreads traffic across them, and they share one hosted Postgres.