Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Atlantus is a language and runtime for web backends.

You write .atl. You run the atlantus CLI. You do not need to install Rust or touch the framework source to build APIs.

You getMeaning
LanguageTyped .atl modules under a fixed app/ layout
CLInew, check, dev, start, db, generators
RuntimeLong-lived HTTP process (state can survive requests)
ViewsHTML templates under app/views

Product cycle

install binary → new project → check → dev → start
  1. Install atlantus
  2. atlantus new my-api
  3. atlantus check
  4. atlantus dev
  5. atlantus start for production-shaped serve

Compared to other stacks

StackAtlantus
PHP + FPMLong-lived atlantus process + .atl handlers
Node + ExpressSame idea: edit routes, restart/reload, ship one binary runtime
Laravel BladeFile views with a small Blade-near syntax
Composer / npmApp deps evolving; start with a single binary

This documentation

SectionUse when
Start hereInstall and first API
LanguageSyntax, HTTP, views, built-ins
FrameworkDB, auth, jobs, CLI
Ship itDeploy and examples

Site: atl-docs.atlantus.network.

Next: Install.

Install

You only need the atlantus binary. You do not need Rust, Cargo, or a clone of the framework to create apps and program in .atl.

Current product version: 0.2.1
Primary asset (today): Linux x86_64

Option A — GitHub CLI (best for private releases)

gh auth login   # once, if needed
gh release download v0.2.1 \
  --repo atlantus-network/atlantuslanguage \
  --pattern 'atlantus-*' \
  --dir /tmp

chmod +x /tmp/atlantus-v0.2.1-linux-x86_64
mkdir -p ~/.local/bin
mv /tmp/atlantus-v0.2.1-linux-x86_64 ~/.local/bin/atlantus
export PATH="$HOME/.local/bin:$PATH"

atlantus --version   # → atlantus 0.2.1

Option B — install script (no compile)

If you have the monorepo checked out or a copy of scripts/install-binary.sh:

./scripts/install-binary.sh --tag v0.2.1
# default install: ~/.local/bin/atlantus
export PATH="$HOME/.local/bin:$PATH"
atlantus --version

Private repo download uses gh or GH_TOKEN / ATL_GH_TOKEN.

Option C — manual from the Releases page

  1. Open the release:
    https://github.com/atlantus-network/atlantuslanguage/releases/tag/v0.2.1
  2. Download atlantus-v0.2.1-linux-x86_64
  3. Install:
chmod +x atlantus-v0.2.1-linux-x86_64
sudo mv atlantus-v0.2.1-linux-x86_64 /usr/local/bin/atlantus
# or: mv … ~/.local/bin/atlantus
atlantus --version

Verify

atlantus --help
atlantus --version

You should see a semver like atlantus 0.2.1.

Product cycle (after install)

atlantus new my-api  →  check  →  dev  →  start

Next: Getting started.

Platforms

PlatformStatus (0.2.1)
Linux x86_64Prebuilt binary on GitHub Releases
macOS / Windows / ARMNot yet as official assets — use Docker or ask for a build

Docker (optional)

If you prefer containers and never install a host binary, see Deploy.

Build from source (contributors only)

Compiling the CLI does require Rust. That path is for people who develop the runtime itself — not for application authors.

# requires rustup + cargo — contributors only
./scripts/install-from-git.sh --tag v0.2.1 --smoke

App developers should stay on the binary path above.

Getting started (≈10 minutes)

Write backends in .atl. Install only the atlantus binary.

You do not need Rust, Cargo, or the framework monorepo.

0. Prerequisites

  • A shell (Linux recommended for the current binary)
  • curl or GitHub CLI (gh)
  • A directory on your PATH (e.g. ~/.local/bin)

Optional later: Docker for container deploy.

1. Install atlantus

Full options: Install. Short path:

gh release download v0.2.1 \
  --repo atlantus-network/atlantuslanguage \
  --pattern 'atlantus-*' \
  --dir /tmp

chmod +x /tmp/atlantus-v0.2.1-linux-x86_64
mkdir -p ~/.local/bin
mv /tmp/atlantus-v0.2.1-linux-x86_64 ~/.local/bin/atlantus
export PATH="$HOME/.local/bin:$PATH"

atlantus --version   # → atlantus 0.2.1

2. Create a project

atlantus new my-api
cd my-api

Optional auth stubs:

atlantus new my-api --with-auth

Everything you ship lives under app/**/*.atl (plus app/views/**/*.html for HTML). There is no main.rs in the product path.

3. Check

atlantus check --path .
# often also:  atlantus check

4. Dev server

atlantus dev --path . --host 127.0.0.1 --port 3000
# hot reload when editing .atl:
atlantus dev --path . --watch
curl -sS http://127.0.0.1:3000/health

5. Database (when you need it)

Scaffolded apps use SQL migrations under db/migrations/:

export ATL_DATABASE_URL="sqlite:./tmp/app.db?mode=rwc"
atlantus db migrate --path .
atlantus dev --path .

6. Production-shaped start

export ATL_ENV=production
export ATL_AUTH_SECRET='replace-with-a-long-random-secret'   # if using auth
export ATL_DATABASE_URL='…'
atlantus start --path . --host 0.0.0.0 --port 3000

Done when

  • atlantus --version prints a semver
  • atlantus new created a project
  • atlantus check is green
  • curl hits your local server

Next

Troubleshooting (app developers)

SymptomFix
atlantus: command not foundInstall binary; put bin dir on PATH
Download 404 / auth errorPrivate release: gh auth login or GH_TOKEN
Wrong OS/arch0.2.1 ships linux-x86_64; see Install
Port already in useatlantus dev --port 3001

Contributors only

Building the CLI from source needs Rust — see Install → Build from source. App authors should never need that section.

Your first API

Build a small JSON API with only the atlantus CLI.

1. Scaffold

atlantus new hello-api
cd hello-api

2. Add a route module

Create app/routes/hello.atl:

module hello

get "/hello" {
  def hello(req) {
    return json({ message: "Hello, Atlantus!" })
  }
}

get "/hello/{name}" {
  def hello_name(req) {
    let name = req.param("name")
    return json({ message: "Hello, " + name + "!" })
  }
}

3. Wire the module

Open the scaffold entry (usually app/main.atl or app/routes/mod.atl) and import your module the same way other route modules are imported in the generated tree. If the scaffold already globs routes, a new file under app/routes/ may be enough — run atlantus check and fix any “unknown module” diagnostics.

Typical pattern:

import routes.hello

(Exact import syntax follows your generated app/ layout — prefer matching existing imports in the scaffold.)

4. Run

atlantus check --path .
atlantus dev --path . --port 3000
curl -sS http://127.0.0.1:3000/hello
curl -sS http://127.0.0.1:3000/hello/Ada

5. HTML page (optional)

get "/hi/{name}" {
  def hi(req) {
    let name = req.param("name")
    return view("hello/show", { "name": name })
  }
}

app/views/hello/show.html:

@extends("layouts/app")
@section("content")
<h1>Hello, {{ name }}!</h1>
@endsection

See HTML views.

Response helpers

APIMeaning
json(body)HTTP 200 + JSON
json(body, 201)Created
status(204)No body
html(body)HTML string response
view(path, data)File template → HTML response

More: Built-in API.

Checklist

  • atlantus check green
  • Route listed when atlantus dev starts
  • curl returns the expected JSON

Next: Project layout.

Project layout

A product app is a directory with atlantus.toml and an app/ tree of .atl files.

my-api/
  atlantus.toml
  .vscode/                # VS Code / Cursor: .atl settings, extension tip, tasks
  app/
    main.atl              # entry / composition
    routes/               # HTTP handlers (*.atl)
    models/
    services/
    repositories/
    jobs/
    events/
    policies/
    providers/
    views/                # HTML templates (*.html)
  config/                 # env / database config (when scaffolded)
  db/
    migrations/           # SQL migrations

Open the project folder in VS Code or Cursor: install the recommended Atlantus (.atl) extension when prompted for syntax highlighting. .vscode/settings.json already associates *.atl and points atl.lsp at the atl-lsp binary if you have it on PATH.

Rules of thumb

PathRole
app/routes/*.atlHTTP routes (get / post / …)
app/services/*.atlBusiness logic
app/models/*.atlDomain shapes
app/views/**/*.htmlTemplates for view(...)
db/migrations/Schema evolution
atlantus.tomlProject name, server defaults

No Rust required for handlers. atlantus new creates this tree for you.

Multi-file modules

// app/routes/users.atl
module users

import services.users as Users

get "/users/{id}" {
  def show(req) {
    return json(Users.find(req.param("id")))
  }
}

The compiler loads app/**/*.atl into one module + route table for the runtime.

Environment variables (common)

EnvUse
ATL_DATABASE_URLSQLite / Postgres URL
ATL_ENVdevelopment / production
ATL_AUTH_SECRETRequired in production when using auth
ATL_CORS_ORIGINSCORS allow list (comma-separated)
ATL_MAX_BODY_BYTESMax request body size

Commands in a project directory

atlantus check --path .
atlantus db migrate --path .
atlantus dev --path . --port 3000
atlantus start --path . --host 0.0.0.0 --port 3000

Next: Syntax.

ATL-S syntax (5 minutes)

Write HTTP apps in short .atl. Sugar expands to the same runtime as classic forms; older verbose styles still work.

Hello route

module app

get "/hello" {
  def hello(req) {
    return json({ message: "hi" })
  }
}

fn main() -> Int {
  return 0
}
atlantus check --path .

Route blocks

get    "/users"      { def index(req)   { return json(list()) } }
get    "/users/{id}" { def show(req)    { return json(get_one(req.param("id"))) } }
post   "/users"      { def create(req)  { return json(create(req.json()), 201) } }
put    "/users/{id}" { def update(req)  { return json(update(req.param("id"), req.json())) } }
delete "/users/{id}" { def destroy(req) {
  if delete_one(req.param("id")) {
    return status(204)
  }
  return json({ error: "not_found" }, 404)
}}
  • One def / fn per block
  • Method + path become HTTP routes
  • Untyped req is the request; omitted return is a response

def and fn

def add(a: Int, b: Int) -> Int {
  return a + b
}

fn add(a: Int, b: Int) -> Int {
  return a + b
}

JSON responses

Write thisMeans this
json(body) / ok(body)HTTP 200 + JSON body
json(body, 201) / created(body)HTTP 201 + JSON body
status(204) / no_content()Status only

Request helpers

Write thisMeans this
req.param("id")Path param {id}
req.json()Parsed JSON body
req.header("authorization")Header value
req.method() / req.path()Method / path

Elegant handlers

Short status + early-exit sugar (desugars to the same natives as above):

Write thisMeans this
not_found(msg?)404 error envelope
unauthorized / forbidden / bad_request401 / 403 / 400
unprocessable(msg_or_fields?)422 validation_failed
let x = require_found(e, msg?)value or 404
let x = require_json(req)body or 422
let x = require_auth(req)user id or 401
require_fields(map, ["a","b"])422 if missing field
get "/users/{id}" {
  def show(req) {
    let user = require_found(db_get("users", req.param("id")), "not found")
    return ok(user)
  }
}

post "/users" {
  def create(req) {
    let body = require_json(req)
    require_fields(body, ["name"])
    return created(body)
  }
}

Null checks with none

let user = find(id)
if user is none {
  return not_found()
}
return ok(user)

Control flow

if n > 0 {
  return ok({ ok: true })
} else if n == 0 {
  return ok({ ok: "zero" })
} else {
  return bad_request("n must be >= 0")
}

Maps and arrays

let user = { "name": "Ada", "id": 1 }
let tags = ["atl", "web"]

Next: Language overview · HTTP & JSON.

Language overview

Atlantus apps are multi-file .atl modules under app/. The CLI compiles them and the runtime serves HTTP routes.

What you write

  • Functions and route handlers in .atl
  • JSON-friendly values: numbers, strings, bools, arrays, maps
  • Null checks with none
  • Modules with import

Types (practical)

TypeNotes
Int, Float, Bool, StringScalars
Arrays / listsOrdered values
Maps / { "k": v }JSON objects
Request / ResponseInferred at the HTTP boundary

Day to day, rely on atlantus check for type and arity errors.

Modules

module users

import services.users as Users

Paths under app/ map to import paths. Prefer matching the scaffold’s style.

Built-ins

HTTP, request, DB, auth, and jobs helpers are built into the runtime.
See Built-in API.

Dev vs production

CommandTypical use
atlantus devFast compile, great DX, optional --watch
atlantus startProduction-shaped process

You write the same .atl either way.

How it runs (optional)

Internally the toolchain compiles to bytecode (and may AOT hot paths). You do not configure compiler crates as an app author — install atlantus and focus on .atl.

Next: HTTP & JSON.

HTTP & JSON

Returning JSON

get "/health" {
  def health(req) {
    return ok({ "status": "ok" })
  }
}

post "/items" {
  def create(req) {
    let body = require_json(req)
    require_fields(body, ["name"])
    return created({ "id": 1, "ok": true })
  }
}

json(body) and ok(body) are equivalent (HTTP 200). Prefer created(body) for 201 and json(body, code) when you need another status.

Status-only

return no_content()   // 204
// or: return status(204)

Path params

get "/users/{id}" {
  def show(req) {
    let id = req.param("id")
    return ok({ "id": id })
  }
}

Headers

let auth = req.header("authorization")
// or early-exit: let uid = require_auth(req)

Content types

HelperTypical Content-Type
json(...) / ok(...) / created(...)application/json
html(...) / view(...)text/html; charset=utf-8

Errors

Unhandled runtime errors become 500 JSON with a request_id. Prefer explicit helpers (stable error codes):

return not_found()
return not_found("user missing")
return unauthorized()
return forbidden("no access")
return bad_request("bad id")
return unprocessable({ "email": "required" })

These desugar to http_json_response with { error, message? } or { error: "validation_failed", fields? }. Classic json({ "error": "not_found" }, 404) still works.

CORS & body limits

EnvMeaning
ATL_CORS_ORIGINSComma-separated origins
ATL_MAX_BODY_BYTESMax body size (default ~10 MiB)

Next: HTML views · Natives.

HTML views

File templates under app/views/**/*.html — Blade-near, intentionally small.

API

APIMeaning
view(path, data)Render file → HTML response (200)
view(path, data, status)Same with status
render_view(path, data)Render → string
html(body)HTML response from a string
render_template(tpl, data)Inline string template

Paths are relative to app/views/ ("hello/show"hello/show.html).

Requires running against a project directory (atlantus dev --path …).

Handler

get "/hello/{name}" {
  def hello_name(req) {
    let name = req.param("name")
    return view("hello/show", { "name": name })
  }
}

Template syntax

ConstructEffect
{{ key }}HTML-escaped
{{{ key }}}Raw
{{-- comment --}}Stripped
@if(x) / @else / @endifConditional
@foreach(list as item) / @endforeachLoop
@extends("layouts/app")Layout
@section("content")@endsectionSection
@yield("content")Layout hole
@include("partials/nav")Partial

Layout example

app/views/layouts/app.html:

<!DOCTYPE html>
<html>
<body>
  @include("partials/nav")
  <main>@yield("content")</main>
</body>
</html>

app/views/hello/show.html:

@extends("layouts/app")
@section("content")
<h1>Hello, {{ name }}!</h1>
@endsection

Principles

  • Escape by default
  • Views are pure (no DB) — handlers pass data maps
  • Prefer file views for real pages

Try it with atlantus dev after adding routes + templates.

Next: Built-in API.

Built-in API (natives)

Handlers call built-in functions provided by the runtime (often called natives). You do not import crates — if atlantus check accepts the name, the host implements it.

This page is the practical catalog for app authors. Prefer atlantus check for arity/type feedback on your version.

HTTP responses

CallResult
json(body) / ok(body)200 + JSON body
json(body, status)JSON with status (e.g. 201)
created(body)201 + JSON body
status(code)Status only (e.g. 204)
no_content()204 status only
not_found(msg?)404 JSON { error: "not_found", message? }
unauthorized(msg?)401 error envelope
forbidden(msg?)403 error envelope
bad_request(msg?)400 error envelope
unprocessable(msg_or_fields?)422 validation_failed
html(body)200 HTML
html(body, status)HTML with status
view(path, data)Render app/views/... → HTML response
view(path, data, status)Same with status
render_view(path, data)Render → string
render_template(tpl, data)Inline template → string

Status helpers desugar to http_json_response / http_status — same runtime as verbose forms.

Request

Call / sugarMeaning
req.param("id")Path param {id}
req.json()Parsed JSON body (or none)
req.header("authorization")Header
req.method()HTTP method
req.path()Path

Early-exit require helpers

Compiler sugar (expand to bind + early return), not separate host APIs:

Call / sugarMeaning
let x = require_found(expr, msg?)Value or 404 not_found
let x = require_json(req)JSON body or 422
let x = require_auth(req)auth_user_id(req) or 401
require_fields(map, ["a","b"])Statement; 422 if a field is missing

Control / null

Call / sugarMeaning
x is noneNull check
x is not nonePresent

Database (when SQLite/Postgres is enabled)

Exact names follow your CLI version — common pattern in product apps:

AreaExamples (check with your binary)
Querydb_query_one, db_query_all
Executedb_execute
Helperslist/get patterns used after atlantus db migrate

Always set:

export ATL_DATABASE_URL="sqlite:./tmp/app.db?mode=rwc"
atlantus db migrate --path .

See Database for a minimal flow. For a full working app tree, atlantus new + migrations is the supported starting point.

Auth (when enabled)

Typical surface (scaffold --with-auth / blog-style apps):

  • Password hash / verify natives
  • Token or session helpers (auth_user_id(req) — used by require_auth)
  • Login rate helpers

Production requires:

export ATL_ENV=production
export ATL_AUTH_SECRET='long-random-secret'

See Auth.

Jobs / events

Enqueue-style natives and a worker process for durable work — see Jobs.

Logging

CallMeaning
log_info(...)Structured/info log (host)

How to discover more

atlantus check --path .
# type errors name unknown or mis-arity natives
atlantus --help

When something is missing, upgrade the atlantus binary from Releases — do not install Rust to “get more APIs.”

Database

Use SQL migrations + ATL_DATABASE_URL. Development usually starts with SQLite; production often uses Postgres.

1. Configure

export ATL_DATABASE_URL="sqlite:./tmp/app.db?mode=rwc"
# production example:
# export ATL_DATABASE_URL="postgres://user:pass@host:5432/dbname"

2. Migrate

atlantus db migrate --path .

Migrations live under db/migrations/ (created by atlantus new or your team’s templates).

3. From handlers

Use the runtime’s DB built-ins (names validated by atlantus check on your binary). Pattern:

get "/users/{id}" {
  def show(req) {
    let id = req.param("id")
    let user = /* db_query_one / project helper for users by id */
    if user is none {
      return json({ "error": "not_found" }, 404)
    }
    return json(user)
  }
}

Prefer putting SQL access in app/repositories/ or app/services/ and keeping routes thin — that is what atlantus new scaffolds toward.

Checklist

StepCommand / env
URLATL_DATABASE_URL
Schemaatlantus db migrate --path .
Serveatlantus dev --path .

Production notes

  • Run migrations in deploy before atlantus start
  • Do not commit secrets; inject via env
  • Prefer Postgres for multi-node

Next: Auth · Natives.

Auth

Atlantus can scaffold auth-oriented routes and uses runtime helpers for passwords and tokens/sessions.

Scaffold

atlantus new my-api --with-auth
cd my-api

Production secret

When ATL_ENV=production, set a strong secret:

export ATL_ENV=production
export ATL_AUTH_SECRET="long-random-secret"
atlantus start --path .

Local dev can omit production env; never ship with a default secret.

Typical routes

Scaffolds and sample apps often expose:

  • POST /auth/register
  • POST /auth/login
  • GET /me (authenticated)

Open the generated app/routes/ files after --with-auth — that is the supported copy-paste base.

Practices

  • Hash passwords only via runtime helpers (never roll your own)
  • Prefer the scaffold’s token/session flow
  • Rate-limit login in production when helpers are available

Next: Jobs.

Jobs & queues

Background work is modeled as jobs (and sometimes events). Simple apps may drain work in-process; durable setups run a worker next to HTTP.

Mental model

HTTP handler → enqueue job → worker processes job

When to use a job

Use a jobKeep in the request
Email, webhooks, slow I/OTiny pure compute
Must survive process restartMust finish before response

Running a worker

Check your CLI version:

atlantus --help
# often: atlantus job work --path .

Deploy HTTP (atlantus start) and the worker as two processes sharing the same app directory and ATL_DATABASE_URL when the queue is durable.

App code

Enqueue from a handler or service using the job built-ins available in your binary (atlantus check will accept valid names). Keep job handlers under app/jobs/ as scaffolded.

Next: CLI.

CLI reference (atlantus)

Install the binary first: Install.

atlantus --version
atlantus --help

Everyday commands

CommandPurpose
atlantus new <name>Scaffold a new app
atlantus new <name> --with-authScaffold with auth stubs
atlantus check --path .Structure + compile
atlantus dev --path .Development server
atlantus dev --watchHot reload on .atl changes
atlantus start --path .Production-shaped serve
atlantus build --path .Compile / emit artifacts
atlantus db migrate --path .Apply SQL migrations

Useful flags

atlantus dev --path . --host 127.0.0.1 --port 3000
atlantus start --path . --host 0.0.0.0 --port 8080

Project root

--path points at the directory with atlantus.toml (your app).
After atlantus new my-api && cd my-api, that is ..

Generators & extras

Depending on version, also available via atlantus --help:

  • gen — route/service stubs
  • fmt — format .atl
  • openapi — OpenAPI from routes
  • job work — worker loop for durable jobs

Not required

You do not need cargo, rustc, or a checkout of the framework to use these commands. Those appear only in contributor docs for people building Atlantus itself.

Next: Deploy.

Deploy

Mental model

install atlantus binary → copy your app → migrate → start (+ optional worker)

No Rust toolchain on the server for app deploy.

1. Install the CLI on the server

Same as Install — download the release binary onto the VPS:

# example: linux x86_64 asset from GitHub Releases v0.2.1
sudo install -m 0755 ./atlantus-v0.2.1-linux-x86_64 /usr/local/bin/atlantus
atlantus --version

2. Copy your app

# from your machine — your project, not the framework source
rsync -a ./my-api/ user@server:/opt/my-api/

3. Environment

export ATL_ENV=production
export ATL_AUTH_SECRET='…'          # if using auth
export ATL_DATABASE_URL='postgres://…'
export ATL_CORS_ORIGINS='https://app.example.com'

4. Migrate & start

cd /opt/my-api
atlantus db migrate --path .
atlantus start --path . --host 0.0.0.0 --port 3000

Optional worker:

atlantus job work --path .    # if available in your version

Put Nginx/Caddy in front for TLS.

systemd sketch

[Unit]
Description=Atlantus app
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/my-api
Environment=ATL_ENV=production
Environment=ATL_AUTH_SECRET=change-me
Environment=ATL_DATABASE_URL=postgres://…
ExecStart=/usr/local/bin/atlantus start --path /opt/my-api --host 0.0.0.0 --port 3000
Restart=on-failure

[Install]
WantedBy=multi-user.target

Health

curl -fsS http://127.0.0.1:3000/health
curl -fsS http://127.0.0.1:3000/ready

Docker

If your team ships a container image that already contains atlantus, deploy that image and mount/copy the app directory. You still do not need Rust on the host — only the image build pipeline (CI) compiles the runtime.

Next: Examples.

Examples & next steps

Learn by building

  1. Install the binary
  2. Getting started
  3. Your first API
  4. Syntax + HTTP + Natives
  5. Views for HTML
  6. Deploy

Minimal recipes (copy into your app)

Health JSON

get "/health" {
  def health(req) {
    return json({ "status": "ok" })
  }
}

Path param

get "/hello/{name}" {
  def hello(req) {
    return json({ "message": "Hello, " + req.param("name") + "!" })
  }
}

HTML view

get "/hi/{name}" {
  def hi(req) {
    return view("hello/show", { "name": req.param("name") })
  }
}

Sample apps

If your team ships sample trees (blog, queue worker), install them as normal app directories and run:

cd path/to/sample-app
export ATL_DATABASE_URL="sqlite:./tmp/app.db?mode=rwc"
atlantus db migrate --path .
atlantus dev --path . --port 3000

You never need cargo to run a sample app.

Editor

VS Code: install the Atlantus / .atl extension when your team distributes it (VSIX or marketplace). The LSP talks to the same language surface as atlantus check.

Docs site

Keep learning

GoalChapter
Types & modulesLanguage overview
DBDatabase
LoginAuth
Background workJobs
CLI flagsCLI