Eight applications built on Garuda's public API. Seven are small enough to read in one sitting, each showing one thing; the starter shows the shape of a whole application and is the one to copy when beginning. They build against the Garuda checkout this directory sits in.
| Example | Run | What it shows |
|---|---|---|
| Todo | swift run todo |
A JSON CRUD API on SQLite (GarudaSQLite): typed routes, validation answered as 422 and 409, migrations, paging |
| Auth | swift run auth |
Sign-up, login and logout: PBKDF2 password hashes, random session tokens stored as digests, authenticate(bearer:state:), an extractor of its own that awaits |
| Streaming | swift run streaming |
Server-sent events, a CSV export written as it is produced, an upload read as it arrives and written to disk with a body limit of its own, and an offloaded route |
| Chat | swift run chat |
Rooms over WebSockets and server-sent events, heard across every shard through Topic |
| WebTransport | swift run webtransport -- --tls-cert cert.pem --tls-key key.pem |
A network probe over HTTP/3: datagram round trips, download and upload throughput on streams, a stream the server opens, an extractor that refuses a session before it starts, and a browser page that trusts a self-signed certificate by its hash |
| Files | swift run files |
A file service: resumable uploads (GarudaUploads) with size limits and SHA-256 digests, the files served back by --static-dir with byte ranges, ETag and a listing |
| Uploads | swift run uploads |
Photo uploads per user: resumable uploads inside group("/users/:user") behind bearer tokens, onCreate recording whose upload it is, the bytes checked for the image type they claim, and an answer with a Location replayed to a client that lost it |
| Starter | swift run starter |
A whole application on PostgreSQL (GarudaPostgres): accounts with JWT access and refresh tokens, roles and admin-only routes, input rules answered as 422, migrations, configuration from the environment, OpenAPI, health and readiness, and a deployment recipe |
cd Examples
swift run chat -- --port 8080 --shards 4 # then open http://localhost:8080
swift test # every example, through app.test
createdb starter
DATABASE_URL='postgres://you@127.0.0.1:5432/starter?sslmode=disable' \
swift run starter serve -- --port 8080 # then open http://localhost:8080/docsThe starter has its own guide, STARTER.md: its layout,
configuration, migrations, the per-shard model, and how to deploy it. Its
database tests run against the PostgreSQL in GARUDA_POSTGRES, as Garuda's
own do, and are skipped without it.
Everything after -- is Garuda's own flags (CONFIG.md): TLS,
HTTP/3, shards and threads, compression, rate limits. Each main.swift has
curl commands to try.
An example is a library target with one function that builds its
Application, and an executable whose main.swift calls it and runs it:
// Sources/todo/main.swift
todoApp(databasePath: path).run()The function is what the tests call, so the tests exercise exactly the routes
the executable serves, through the real engine. Configuration an application
reads from its environment (a database path, a directory) is read in
main.swift and passed in.
The package depends on Garuda's products -- Garuda, and GarudaSQLite,
GarudaPostgres, GarudaUploads and GarudaTesting where an example needs
them -- and on Vega, the runtime Garuda is built on, for what Garuda does not
export: SHA-256 (VegaCrypto, in the webtransport example), a runtime for
work outside the server (Vega, in the starter's withServices), and file
system calls that run on the blocking pool (VegaFS, in the streaming, files
and uploads examples).
- State is per shard. Garuda runs a process per shard, and each shard
serves on several threads.
app.statebuilds a database handle in each shard as it starts; nothing made beforerun()is shared between them, and what a shard's threads share isSendable. What every shard must see lives outside the process: the SQLite file, aTopic. - SQLite with several shards works: each shard has its own connections to the same file, in write-ahead-log mode, and writes wait for each other. For more write traffic than one file takes, use PostgreSQL; the handler code looks the same.
- Passwords cost CPU on purpose. The auth example hashes with 600,000
iterations on Vega's blocking pool. Run it with
--rate-limit, and behind TLS. - Blocking work goes off the shard's threads. A handler that blocks, as
the streaming example's
statdoes, is anapp.offloadroute and runs on the blocking pool. VegaFS's calls, which the uploads use, run there by themselves. - Uploads in the streaming example are read as they arrive, with
onStreamingBody, and go to a partial file that is renamed when complete. Every other route is given its body whole, so for those the route'smaxBodySizeis also what a request may hold in memory; a streaming route's limit is only how much it takes. Serve the directory with a separate route or--static-dirif they should be downloadable. - Resumable uploads in the files example are
app.resumableUploads: the body goes to disk as it arrives, a client that speaks the draft protocol can ask how much arrived and send the rest after a dropped connection, and the handler is called once, with the whole file on disk, to move it into place. The files go back out through--static-dir, whichmain.swiftadds to the command line, so the application writes no download code at all. - Whose upload it is is decided once, on the request that creates it. The
request that completes an upload is a later one, perhaps on another
connection; the uploads example's
onCreaterecords the owner from the group's authentication, and the completion handler reads it fromupload.metadata, which no client can write.Content-Typeand the file name are the client's word, so the example checks the first bytes before it keeps a photo. - WebTransport needs HTTP/3, and so TLS. A browser trusts a self-signed
certificate over WebTransport only through
serverCertificateHashes, and only an ECDSA one valid for at most 14 days; the webtransport example'smain.swifthas theopensslline. A session lives on one shard, so sessions that must hear each other need something outside the shard to carry it, as the chat example does withTopic.