Your first application

From cataract new to a page, an API route and a layout.

Updated 2 min read

cataract new writes the smallest project that compiles: a config file, a layout, an index page, a health endpoint and a stylesheet.

cataract new blog && cd blog

A page

Every file under app/routes declares its module explicitly, because two index.chpl files in different directories would otherwise collide on the module name Chapel derives from the filename.

module PageIndex {
  use Cataract;

  proc page(ctx: Context, ref meta: PageMeta): string {
    meta.title = "Hello";
    meta.description = "A Cataract application";

    var h = new MarkupBuilder();
    h.el("h1", "Hello from Chapel");
    h.open("p");
    h.text("Edit ");
    h.el("code", "app/routes/index.chpl");
    h.text(" and rebuild.");
    h.close();
    return h.done();
  }
}

The return value is the page body. It goes into a layout, and the result goes into the document shell that writes <!doctype html>, the <head> and the closing tags.

An API route

A module that exports procedures named for HTTP methods is an API route rather than a page. Declaring both page and method handlers in one file is a build error.

module ApiHealth {
  use Cataract;

  proc get(ctx: Context): Response {
    var body = new JsonBuilder();
    body.beginObject();
    body.field("status", "ok");
    body.endObject();
    return jsonResponse(body.done());
  }
}

A layout

A layout wraps every page that names it. It receives the rendered page as slot and may still modify meta, because layouts run after the page and before the document shell.

module RootLayout {
  use Cataract;

  proc layout(ctx: Context, slot: string, ref meta: PageMeta): string {
    var h = new MarkupBuilder();

    h.open("header", "class", "site");
    h.el("a", "blog", "href", "/");
    h.close();

    h.open("main");
    h.raw(slot);
    h.close();

    return h.done();
  }
}

Build, run, iterate

cataract build          # scan, generate, compile
./dist/blog --port=8080 # every server setting is a config const
cataract dev            # rebuild and restart on change

cataract dev content-hashes the sources rather than comparing timestamps, so an editor that rewrites a file with identical bytes does not trigger a rebuild, and a checkout that rolls timestamps backwards does.