Skip to content

Quick start

This page takes you from nothing to a running Nola program — and the first run needs no API key.

Terminal window
npm create nola

The scaffolder asks for a name and a template — starter (the default: typed extraction that runs offline), empty (just nola.config.ts and tsconfig.json), or one of the curated examples. Non-interactive: npm create nola my-app -- --template extract-resume. Nola needs Node ≥ 22.

Pick VS Code at the editor prompt (or pass --ide vscode) to get a launch.json for F5 debugging and a recommendation for the Nola extension — see Editor setup.

Terminal window
cd my-app
npm install
npm start

npm start runs nola run src/main.ts. The starter’s nola.config.ts uses replay("./nola.replay.jsonl"), so the answers come from a ledger committed with the project — no key, no network. These are the two files it runs:

src/person.tsi
export interface Person {
name: string;
age: number;
employer: string;
job: string;
}
export infer function extractPerson(.message: string) {
const person = ask ..`the person described in the text`<Person>;
return person;
}
src/main.ts
import { extractPerson } from "./person.tsi";
const person = await extractPerson(
"Alice Smith, 32, is a staff engineer at Acme Corp working on distributed systems.",
);
console.log(JSON.stringify(person));

Expected output:

Terminal window
{"name":"Alice Smith","age":32,"employer":"Acme Corp","job":"staff engineer"}
  • .message is a contextual parameter — its value is shown to the model.
  • ask ..`…`<Person> is an extractor: it asks the model for a Person, and ask resolves it. Raw intents are resolved with ask, never a bare await.
  • The .tsi import keeps its extension, and plain TypeScript awaits the returned intent — that is what runs the inference.

Edit nola.config.ts:

nola.config.ts
import { openai } from "@nola-lang/providers";
import { defineConfig } from "@nola-lang/runtime";
export default defineConfig({
providers: {
// Reads OPENAI_API_KEY from the environment at the first ask.
default: openai({ model: "gpt-5-mini" }),
},
});

Put OPENAI_API_KEY=… in a .env file at the project root (the dev loader reads it; production reads the real environment) and run npm start again. anthropic({ model }) and google({ model }) work the same way — see Providers.

Why switch now? The replay ledger is strict: once you edit a prompt or a type, the recorded entry no longer matches and replay fails with NOLA3008 instead of silently calling the network. Either switch to a live provider or re-record the ledger — see Record and replay.

Terminal window
npx nola check # type-checks .tsi and .ts together, positions mapped back to .tsi
npx nola build # lowers every .tsi into dist/ (+ a self-configuring dist/nola.config.js)

nola build writes dist/src/person.tsi.js (with a source map and a .tsi.d.ts next to it) and a dist/nola.config.js that the built modules import, so they run under plain node with no loader. It compiles .tsi files only — a plain .ts entry like src/main.ts is not part of its output; see Deploying for the production recipe.

nola-lang is a devDependency; the app itself depends on @nola-lang/runtime and @nola-lang/providers.

Next: Add Nola to an existing project