Elements vs Meteor
Meteor is an open-source, full-stack JavaScript platform for building web and mobile apps in JavaScript or TypeScript, made by Meteor Software and released under the MIT License. One project holds the server code, the browser code and the build, and the meteor command creates, runs and builds it. Its data layer is built on MongoDB. The server publishes data, the browser subscribes to it and keeps a local copy in Minimongo, an in-browser database, and the page updates on its own as the data changes. Methods are its built-in way to call the server. A Method defined in code the browser also loads runs there first as a stub, so the screen shows the result of an action before the server confirms it. Meteor includes its own build tool and an accounts system, works with front ends such as React, Vue, Blaze, Svelte and Solid, and draws packages from both npm and Atmosphere, its own package registry. Meteor Software also runs Galaxy, a hosting service for Meteor apps.
Elements is an integrated app environment, a full-stack platform in the same spirit, built for people and their agents. At its center is a project server that runs while you work, and around it the same system includes a package installer, a test runner, a bundled Postgres database, and a deploy command for any Ubuntu server you reach over SSH. It ships with a default framework, @elements/app, with real-time data built in: pages written in Elements HTML (standard HTML with reactive expressions), typed server functions the browser calls directly, sets of database rows that stay in sync in every open browser, sessions, background jobs and email. Elements apps are written in TypeScript and run on Node.js.
Elements carries Meteor's central ideas forward: live data from the database to the page, screens that update before the server answers, and one command to start. The difference is what sits underneath them. Data lives in typed Postgres tables you query with SQL. npm is the one package system, with no second registry to track. Every page arrives from the server fully rendered, so visitors and search engines see content before any script runs. A project server holds the build state, so on every save it is updated almost instantly, and you and your agent see every build error, from type checking to a failing test, from one command. A deploy to your own server often finishes in under a second.
What Meteor Gets Right
Meteor makes a web app one project in one language, with the client, the server, the database and the build tool set up together. You run one command and all of it works.
Two of its ideas shape what developers expect from an app:
- Latency compensation. When the browser calls a Method, the stub runs first to "simulate the result of what the server's method will do, but without waiting for the round trip delay". The screen moves the moment the user acts.
- Publish and subscribe. A publication on the server feeds a subscription in the browser, and the page follows the data as it changes, with no polling code to write.
Elements keeps both ideas, and the feeling of working in one coherent system. It comes from the Meteor community: Chris Mather, who created Elements, co-created Iron Router, a widely used router for Meteor. A Meteor developer will feel at home.
How Meteor Concepts Map to Elements
Each Meteor concept below has a counterpart in Elements. Three Elements terms come up throughout. An @rpc is a typed server function the browser calls directly. A LiveTable is a set of database rows that stays in sync with every browser showing it, declared once on the server. A LiveView is the part of a LiveTable one page is watching, and it is what the page reads and writes.
| Meteor | Elements |
|---|---|
meteor create, meteor run |
elements create, elements start |
| Community routers such as Iron Router | Routes declared in code, built in |
Meteor.call to a Method |
A direct, typed call to an @rpc function |
| Method stubs | Optimistic LiveView writes |
Meteor.publish and Meteor.subscribe |
A LiveView returned from a route or an @rpc |
Meteor.publish with arguments, to scope what a user sees |
LiveTable partitions |
| MongoDB | Postgres, bundled and set up for you |
| Minimongo in the browser | The rows of a LiveView, held in the browser |
| Oplog tailing or change streams | Postgres NOTIFY/LISTEN |
| Tracker and ReactiveVar, Meteor's reactivity system | Reactive templates |
| Blaze, React, Vue, Svelte or Solid | Elements HTML, server rendered and hydrated |
| Accounts packages | Session, built in |
The email package |
Email, written in Elements HTML |
| Community job and cron packages | Jobs and cron schedules, built in |
Meteor.settings |
config.jsoc |
| Atmosphere and npm | npm |
A local packages/ folder |
Local npm packages, watched like source |
meteor test with a driver package |
Tests that run inside the build |
| Community migration packages | SQL migrations, built in |
| Galaxy, Meteor Up or Docker | elements deploy over SSH |
Pub/Sub, with Postgres as the Coordinator
Publish and subscribe needs a coordinator: something that notices a change and tells every open page. In Meteor that is MongoDB. Publications return cursors, the browser keeps a Minimongo cache, and the server learns of changes by tailing the oplog (MongoDB's log of every write) or through change streams. Both need MongoDB to run as a replica set, its replication mode. Where neither is available, Meteor polls each query, every 10 seconds by default, and compares the results, which its docs call "very resource-intensive for both the Meteor app and the database".
In Elements the coordinator is Postgres, through its built-in NOTIFY/LISTEN channels, which the bundled database provides with no extra setup. You declare a LiveTable once on the server, and a route opens a LiveView of it, so the page renders with the rows already in the first response. A write through a LiveView is broadcast on the table's channel. Every app server connected to the database listens on that channel, and each watching browser then patches the changed row in place. A trigger on the table makes writes from psql, a job or a webhook live too (recipe).
Because the rows come from SQL, a LiveTable can hold a join or a projection through a custom select, not only one table's rows. For messages that are not rows, such as a typing indicator, a Channel gives you pub/sub on the same Postgres mechanism.
Latency Compensation Is Built Into Live Data
Latency compensation means the screen shows the result of an action before the server confirms it. Meteor gets there with a Method stub: the Method runs in the browser and on the server, and a separate publication sends the confirmed data down.
In Elements, writes through a LiveView work this way by default. insert, update and delete on a LiveView are optimistic by design: the browser updates at once, the server reconciles in the background, and a failed write reverts the change on screen. The same LiveView serves the reads and the writes, so there is no publication to keep in step with a method. The same insert works on the server, so a test exercises exactly the write the page makes.
Here is a live comment list. In Meteor with React, it takes three files: a shared file that declares a Mongo.Collection and a Method that checks the text and inserts it, a server file that publishes the collection, and a component that subscribes with useSubscribe, reads the comments with useFind or useTracker, shows a loading state until the subscription is ready, and calls the Method on submit. Meteor's React tutorial builds the same pattern for a task list, and react-meteor-data documents the hooks.
In Elements, it takes a LiveTable, a route and a template, with the comments table created by a SQL migration:
app/shared/services/comments.tsElementsimport { LiveTable } from "@elements/app"; export interface Comment { id: string; text: string; createdAt: Date; } export let comments = new LiveTable<Comment>();
app/pages/comments/index.tsElementsimport { Request, Response } from "@elements/app"; import { comments } from "#app/shared/services/comments"; import html from "./template"; export default function route(req: Request, res: Response) { return new html({ comments: comments.view() }); }
app/pages/comments/template.ehtmlElementsimport { LiveView } from "@elements/app"; import { Comment } from "#app/shared/services/comments"; function onAdd(form: { text: string }, comments: LiveView<Comment>) { comments.insert({ text: form.text, createdAt: new Date() }, () => form.text = ""); } <html (comments: LiveView<Comment>, private form: { text: string } = { text: "" })> <ul> <li e:for={c of comments.sort((a, b) => +a.createdAt - +b.createdAt)}>{c.text}</li> </ul> <form onsubmit={() => onAdd(form, comments)}> <input value={form.text}> <button>post</button> </form> </html>
The Elements page arrives with the comments already in it, so there is no loading state to write, and the compiler checks every field the template reads against Comment.
Pages Arrive Rendered, Then Come Alive
Where a page is rendered decides what a visitor sees first. Meteor's design is "data on the wire": the server sends data, and the client renders the page from it. Meteor can also render on the server with its server-render package, which you wire up for your front end: render to a string on the server, then hydrate on the client.
In Elements, server rendering is how every page works, with nothing to wire. Pages are written in Elements HTML, a template language that renders on the server by design. The first response is the complete page, so search engines and first-time visitors see content at once. The browser then attaches to that HTML and keeps it reactive.
Elements HTML is standard HTML with a narrow extension: {} expressions, templates that declare typed attributes like function parameters, and e:if, e:for and e:switch. Binding an input with value={form.text} is two-way: typing updates the data, and changing the data updates the input. When data changes, the runtime patches the DOM at the smallest level the change needs. Blaze developers will recognize the model: HTML with expressions that update on their own. Elements adds server rendering and a compiler that checks every expression.
Every Build Error Is Caught, Templates Included
Both platforms let you write TypeScript. The difference is whether the types are checked. Meteor compiles TypeScript, and its plugin's own README says it "does not attempt to provide type checking (just compilation)".
Elements compiles the TypeScript checker into its binary and makes it part of the build, for both the server and the browser, with no separate tsc step to run. Templates are TypeScript to the checker, so a misspelled field inside {} is a build error with the same message a .ts file would give.
The compiler also guards the line between server and browser. Calling sql(), tx() or session.login() from code the browser can reach is a compile error at the call site, so server-only code cannot reach the browser by accident.
One Package System: npm
Meteor's docs describe "two package ecosystems": Atmosphere packages built for Meteor, and npm packages. Elements has one, and it is npm.
npm packages install as is into a real node_modules tree, ESM and CJS alike. Downloads are cached per machine, so a package you have installed once in any project lands in the next without a download. The installer is part of the build, so the app is built and tested against a new package on the same loop.
A package you are writing yourself is installed from your disk with the version local. It is watched like your own source and deploys with the app. That covers what Meteor developers use a local packages/ folder for, without a second package system.
Both Run on Node.js
Meteor apps run on Node.js. In development they run inside the meteor tool, and packages go in through meteor npm and Atmosphere.
An Elements app is a Node.js app. It is TypeScript compiled for Node.js, and a route's req and res are Node's own request and response objects with additions. The tooling is a separate program that builds, tests, migrates and deploys the app. The default framework, @elements/app, was designed alongside it.
A Project Server, Not a Dev Server
meteor run starts a development server that rebuilds and reloads the app as you edit. elements start starts your app and a project server that runs while you work, and that server answers build queries from your editor, your terminal and your agents.
The project server keeps the build graph and build state in memory. On every save, the build state is updated almost instantly, because the server already knows the build and redoes only what the change touched. It builds in milliseconds and answers agents and humans in microseconds. Your terminal, your editor and any number of AI agents share the same build state. An agent that runs elements build -json after every edit gets the current build errors and test results back in microseconds, because the server already has them.
Tests and Migrations Run in the Build
Meteor runs tests with meteor test, a separate process with a driver package, which its docs suggest running on a second port beside the app. Tests reset the database themselves, in a beforeEach block or with a package such as xolvio:cleaner. For schema changes, Meteor apps use community packages such as quave:migrations.
In Elements, the test runner is part of the build. It knows which tests a change affects and runs only those, on concurrent workers. Each test runs inside a Postgres transaction that rolls back when it ends, so there is no cleanup code. A failing test is a build error in the same stream as a type error, and the new version does not go live until its tests pass.
Migrations are SQL files, and the build applies a new one as soon as you save it. Edit one in development and Elements rolls it back and applies the new version. Pending migrations apply to the test database first, then to the app database. Each batch runs in one transaction that succeeds or rolls back in full.
Deploy to Your Own Server, Often in Under a Second
Meteor's docs recommend Galaxy, a hosting service built for Meteor apps. Its docs also describe Docker, and Meteor Up, a third-party tool that deploys over SSH by automating meteor build and copying the resulting bundle to your server.
elements deploy also ships over SSH, to any Ubuntu server from any provider you choose. It provisions the machine, runs the bundled Postgres on it, and brings up a built-in load balancer with automatic TLS from Let's Encrypt. That bundled Postgres suits a prototype or a single-server app; for several machines, or managed backups and scaling, point DB_HOST at a hosted Postgres such as DigitalOcean Managed PostgreSQL or Amazon RDS (setup). The project server on that machine talks to yours directly, so only changed files transfer and build. A first deploy takes a few seconds, and later deploys often land in under a second.
Every deploy runs the tests on each machine, and pending migrations run once, on the first machine. The new release goes live with an atomic swap, and a failed deploy leaves the previous release serving. One commodity server is a complete deployment.
Questions
Is Elements like Meteor?
Yes. A Meteor developer will recognize live data, optimistic writes and one command to start. The differences are Postgres in place of MongoDB, npm as the one package system, pages rendered on the server, and a project server that builds in milliseconds and answers in microseconds.
Does Elements use MongoDB?
No. Elements bundles and manages Postgres, and its real-time layer runs on Postgres NOTIFY/LISTEN. Your data lives in typed tables you query with SQL.
Does Elements have latency compensation like Meteor Methods?
Yes. Inserts, updates and deletes through a LiveView are optimistic by default: the browser updates immediately, the server reconciles, and a failed write reverts on screen. The same LiveView serves the reads and the writes, so there is no publication to keep in step with a method.
Can I use npm packages with Elements?
Yes. npm is the one package system in Elements, and npm packages install as is into a real node_modules tree. Installing is part of the build, so the app is built against a package as soon as it lands.
Can I use React, Vue or Blaze with Elements?
Elements uses its own reactive HTML language in place of a front-end library: standard HTML with typed templates, expressions and two-way binding, rendered on the server and hydrated in the browser. It adds {} expressions and a few e: attributes to HTML, so anyone who knows HTML and TypeScript already knows most of it.
How does deploying Elements compare to Galaxy?
Elements deploys over SSH to any Ubuntu server you choose, with Postgres, a load balancer and TLS set up on the machine. Only changed files transfer, so later deploys often finish in under a second.