Skip to content
KidevPublic

About

Build complete web systems with QML

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

SynQt

SynQt

SynQt is a framework for building complete web systems in Qt and QML, with no third party servers to stand up. You write your application as a set of entities. One is the client (QML compiled to WebAssembly for the browser, and to a native app for Windows, macOS, and Linux from the same code). One is the web edge, the native process that serves the client and faces the internet. Beyond those you add what your system needs: a database, a cache, an API gateway, a jobs runner, an auth service, or anything custom. Each entity is its own folder and its own binary, runs on the same machine or a different one, and talks to the others through typed connect points. SynQt handles the transport, the serialization, the reconnection, the authentication between users and the edge, and the authentication between entities, with a secure default at every step.

Documentation, tutorials, and the full reference: synqt.org.

Quick start

curl -fsSL https://get.synqt.org/install.sh | sh   # macOS and Linux
synqt new my-app
cd my-app
synqt dev

On Windows, in PowerShell: irm https://get.synqt.org/install.ps1 | iex. If you already have Python, pipx install synqt gets you the same CLI from PyPI.

The CLI pins the rest of the toolchain (the Qt SDK and the Emscripten compiler) to one version per project, so every machine builds with the same versions. synqt doctor prints the exact aqt and emsdk commands that install whatever is missing. The full walkthrough is in getting started.

What a system looks like

A project is a set of entities. The client and the web edge are always there, and you add whatever else the system needs. Every entity has its own folder, inside the folder that entities of its type share. Everything one entity is made of is in one place, and two databases never write over each other.

your-app/
  synqt.yaml            # the topology, the security policy, and what crosses each link
  CMakeLists.txt        # four lines, and hands the build to what synqt writes in generated/
  client/app/           # the browser UI (WebAssembly), and the desktop app
  web/edge/             # the web edge (serves the client, faces the internet)
  db/relational/store/  # a persistence entity (embeds SQLite, or masks another engine)
  cache/hot/            # an in memory cache entity

Entities share connect points instead of writing network code. A connect point is a live object that exactly one entity owns and the others mirror. It declares, once and on itself, the typed shape of what may cross it.

connect_points:
  - owner: edge
    consumers: [app]
    export: |
      model items(string[280] text, string[80] author, bool done)  // only these cross
      slot add(string[280] text)          // the browser asks, the edge decides
      signal rejected(string[120] reason)

Property changes and signals flow from the owner to the consumers. Calls flow the other way, and the owner decides whether to honor them. The browser reaches the edge's connect points through Server. One entity reaches another's by that entity's name (Store.find(id)). Inside a connect point's own function, Caller says who is asking, so the owner can authorize every request.

Some browsers give Qt no WebGL context: a policy can disable it, or a driver can be blocked. SynQt checks before the app starts and draws in software when there is none, which covers ordinary 2D Qt Quick completely. The few things that do need a GPU show a notice in place of the content rather than a blank rectangle. See graphics.

A cache, a document store, or an API gateway is an entity like any other. You build and deploy it with the rest of the project, and you do not configure and secure it as a separate product. When you want a particular engine behind one, a provider backs the entity with it. The entity's connect points and the security model around them stay the same.

Drawing it, and watching it

synqt design opens an editor that draws the project on this machine. Entities are nodes and connect points are the lines between them. Apply writes synqt.yaml and the QML a new entity needs. The same editor runs on the site with nothing behind it, so you can sketch a system and export it as a project before installing anything.

A running system is spread over several processes on several machines, linked by connections a browser never shows you. synqt add entity ops --type monitor adds an operations entity that keeps the record and serves a console for it. One line of configuration makes every other entity report to it. A click in the browser then shows up as one trace through every entity it touched. Monitoring is off until you add it. An entity that reports to a monitor pays one atomic read per instrumented call site when nothing is listening. See monitoring.

Security is on by default

  • The browser to edge link is TLS (wss), the user signs in server side (the client never holds a secret), the request origin is checked, and every call is authorized on the edge.
  • Entity to entity links are mutual TLS against a project private certificate authority, on one host over loopback or across hosts. A permission protected local socket is an explicit opt in for co located, equally trusted entities.
  • The topology is an allowlist. An entity reaches only what it is declared to consume. A database is never reachable from the browser and never faces the internet.

Read security before deploying, and deploying a SynQt system when you do.

Performance

A value changes on the server and every connected client has to see it. One publisher, 100 subscribers, saturating, 256 byte payload, on a 32 core Linux host with Qt 6.12.0 and Node 24.20.0, every column from one session. Deliveries per second:

cores SynQt, replicas: N Node cluster, built-ins only SynQt, threads: N
1 136k 124k 135k
2 298k 247k 241k
4 599k 489k 242k
8 1.18M 914k 237k

replicas: N and Node's cluster add capacity by running more processes, and each process holds its own copy of the value. SynQt leads the built-ins column by 10% on one process and by 29% on eight. That column is node:http with a hand written WebSocket implementation, which is faster than what most deployments run. Socket.IO (the usual choice) and Next.js (which ships no WebSocket server, so its live path streams server-sent events) are compared on the fan-out workload in benchmarks/.

threads: N spreads a web edge's sockets across IO threads inside one process, where all 100 subscribers still share a single value. It stops improving after two cores. The process columns keep scaling but cannot serve that case: making N processes agree on one value costs a broadcast between them, and these numbers do not include it.

Most application code goes the other direction. The client asks the server to do something and waits for the answer. In SynQt that is a connect point's returning slot, and the Next.js feature shaped the same way is a Server Function. Measured on the same host with --work echo (an empty function body), p50 by concurrent callers:

1 caller 128 callers calls/s at 32 callers
SynQt, returning slot 0.016 ms 1.6 ms 81k
Node 24, bare JSON POST 0.050 ms 6.2 ms 21k
Next.js on Node 24, Server Function 0.351 ms 39.8 ms 3.1k

Two things make up that gap. React's machinery around a Server Function costs six to seven times what the same Node process costs answering a plain POST. The rest is that a SynQt caller already holds its connection, while both Node columns make an HTTP request per call: a difference in connection handling between the two designs. The Next.js column sends the request React's own client runtime sends, so the framework stays in the path.

Every harness, the committed baselines, what each number does and does not support, and the caveats are in benchmarks/. The deployment docs plot the fan-out data under running one edge on more than one core.

Where to go next

This repository

This is the framework itself: the runtime libraries (src/), the synqt command line tool and the contract generator (tools/), the test suites (tests/), the benchmarks (benchmarks/), the worked example systems (examples/), and the documentation that becomes synqt.org (docs/).

To work on it, start with the developer guide, which maps the codebase and explains how to build and run the suites. The generated C++ class reference documents the runtime itself.

License and contributing

SynQt's own source code is licensed under Apache-2.0 (see LICENSE and NOTICE). An application you build with SynQt inherits the license of the Qt build you use. With open source Qt, the browser client is GPLv3. It is served to every visitor, so you must publish its source. The server side stays private if you self host it. A commercial Qt license lets everything be proprietary. The full analysis, with diagrams, is in licensing.

Contributions are welcome under the CLA in CLA.md. See CONTRIBUTING.md for the SPDX header convention and code style.

Target Qt version

SynQt targets Qt 6.12.0 and the Emscripten version Qt pins to it (5.0.5). These versions matter because the browser transport (QtRO over a WebSocket QIODevice), the mesh transport (QtRO over mutual TLS), the WebSocket upgrade verifier in QHttpServer, OAuth2 with PKCE on by default, and the bundled SQLite driver all depend on current Qt. The build tool pins them so every entity and every contributor gets the same toolchain.

About

Build complete web systems with QML

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages