Quick Start
Build a real full-stack app — an API, a database, and a website — and deploy the whole thing with one command. About five minutes.
We'll build a small guestbook: a page with a form, an API that stores and lists the messages, and a database behind it. Along the way you'll touch all three Drift services — Atomic for the API, Backbone for the data, and Canvas for the site — and never leave your terminal.
1. Install the CLI
Everything on Drift happens through the drift command-line tool. If you have Go installed,
one command puts it on your PATH:
go install github.com/ondrift/cli/cmd/drift@latest
Confirm it's there:
drift --version
2. Log in to your account
Drift is in closed alpha, so accounts are handed out by the platform admin — you'll have been sent a username and password. Use them to sign in:
drift account login
You'll be prompted for your username and password. The CLI keeps you signed in afterwards, so this is a one-time step on each machine.
3. Create a slice
A slice is your project's own private corner of Drift — its own functions, data, site, and URL, fully isolated from everyone else's. Make one for the guestbook:
drift slice create guestbook
That opens the configurator in your browser, where you dial in how much memory, storage, and so on the slice gets and watch the price update live. For this guide the defaults — a free Hacker slice — are more than enough. To skip the browser and take the free slice straight away (handy in scripts and CI):
drift slice create guestbook --free # --headless is an alias
Then set it as the active slice so the commands below know where to deploy:
drift slice use guestbook
4. Lay out the project
A Drift project is just folders and a Driftfile. Each Atomic
function lives in its own folder under atomic/; your site lives under canvas/.
Here's the shape we're about to build:
guestbook/
├── Driftfile
├── atomic/
│ ├── sign/ # POST: save a message
│ └── entries/ # GET: list messages
└── canvas/
└── index.html # the page visitors see
5. Write the API
An Atomic function is a normal source file with a one-line directive on top. The
directive — not a config file — declares how the function is reached: the HTTP method, the
route, and whether it needs authentication. Here's the endpoint that saves a message, in Go
(atomic/sign/sign.go):
// @atomic http=post:sign auth=none
package main
import drift "github.com/ondrift/sdk/go"
func PostSign(body map[string]any, _ drift.Request) (int, string, any, map[string]string) {
id, _ := drift.NoSQL.Collection("guestbook").Insert(body)
return 201, "Created", map[string]any{"id": id}, nil
}
And the one that lists messages back (atomic/entries/entries.go):
// @atomic http=get:entries auth=none
package main
import drift "github.com/ondrift/sdk/go"
func GetEntries(_ drift.Request) (int, string, any, map[string]string) {
all, _ := drift.NoSQL.Collection("guestbook").List(nil)
return 200, "OK", all, nil
}
Each function returns the HTTP status, a short status message, and the response body (the SDK
serialises objects to JSON for you); in Go and Rust an optional headers map comes last —
nil when you don't need one. The directive does the routing —
http=post:sign means "POST, reachable at sign" — so there's no router to
wire up.
drift.NoSQL.Collection("guestbook") is the data layer — the SDK talks to
Backbone for you, and the collection springs into existence the
first time you write to it.
6. Add the page
Canvas hosts your static site. Its best trick: a Canvas page can call its own functions on the
same origin — the browser just fetches /api/sign and
/api/entries, with no CORS to configure and no separate API domain. Save this as
canvas/index.html:
<h1>Guestbook</h1>
<form id="form">
<input name="name" placeholder="Your name" required>
<input name="message" placeholder="Your message" required>
<button>Sign</button>
</form>
<ul id="entries"></ul>
<script>
fetch("/api/entries").then(r => r.json()).then(rows => {
rows.forEach(e => entries.innerHTML += `<li><b>${e.name}</b>: ${e.message}</li>`);
});
form.onsubmit = async (ev) => {
ev.preventDefault();
await fetch("/api/sign", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(Object.fromEntries(new FormData(form)))
});
location.reload();
};
</script>
7. Declare it in a Driftfile
The Driftfile ties the project together: it lists your
functions by name, the collection they use, and your site — all at the top level (the file
is the project). Save this as Driftfile at the project root:
name: guestbook
atomic:
functions:
- sign
- entries
backbone:
nosql:
- guestbook
canvas:
sites:
- ./canvas
8. Deploy everything
One command reads the Driftfile, creates the collection, compiles and ships both functions, and publishes the site:
drift project deploy
Before changing anything, the CLI shows you exactly what it will create and whether your slice is free
or what it costs — then asks you to confirm. (Deploys only ever create or grow a slice; to
shrink one, use drift slice resize.)
9. See it live
Your slice answers at <username>-guestbook.ondrift.eu, with TLS already handled. Open
that URL to use the guestbook in the browser, or hit the API directly:
# sign the guestbook
curl -X POST https://<username>-guestbook.ondrift.eu/api/sign \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "message": "Hello from Drift!"}'
# read it back
curl https://<username>-guestbook.ondrift.eu/api/entries
10. Inspect it from the terminal
You can read and poke at your slice's data without leaving the shell:
# list what's in the guestbook collection
drift backbone nosql read --collection guestbook
# store a secret (injected into your functions on the next deploy)
drift backbone secret set API_KEY=sk-12345
# push a message onto a queue
drift backbone queue push notify '{"to": "alice", "text": "welcome!"}'
# cache a value for an hour
drift backbone cache set greeting "hello world" --ttl 3600
# check the slice's health and usage
drift backbone status
Write in your language
Functions can be written in Go, Python, Node.js, Ruby, PHP, or Rust — the CLI detects the language from your source files. The same "sign" endpoint looks like this in Python…
# @atomic http=post:sign auth=none
import drift
def post_sign(body, req):
entry_id = drift.nosql.collection("guestbook").insert(body)
return 201, "Created", {"id": entry_id}
…and in Node.js:
// @atomic http=post:sign auth=none
const drift = require("@ondrift/sdk");
async function postSign(body, req) {
const id = await drift.nosql.collection("guestbook").insert(body);
return [201, "Created", { id }];
}
module.exports = { postSign };
Deploy any of them the same way — with the Driftfile, or one at a time with drift atomic deploy ./atomic/sign.
Take your data and go
Drift has no lock-in. At any point you can snapshot a slice and download the whole thing — source code, database contents, secrets, and static sites, with no Drift-specific files in the archive:
drift slice snapshot create --name my-backup
drift slice snapshot download <snapshot-id>
Optional: use your own domain
Your slice already answers at <username>-guestbook.ondrift.eu, but you can point your
own hostname at it — Drift verifies ownership and issues the TLS certificate for you. Add a domain
to your active slice:
drift slice domain add guestbook.example.com
The CLI prints two DNS records to create at your registrar:
# 1. prove you own the domain
_drift-challenge.guestbook.example.com. TXT "drift-verify=<token>"
# 2. route the hostname to your slice
# (or an A record to the platform's IP at a zone apex, where CNAMEs aren't allowed)
guestbook.example.com. CNAME ingress.ondrift.eu.
Once DNS has propagated, verify — Drift checks the TXT record and starts issuing the certificate:
drift slice domain verify guestbook.example.com
Watch it go live (and see every domain on the slice) with:
drift slice domain list
When the status reads active, your guestbook is live on your own domain — HTTPS and all.