Skip to content

Quickstart: an App from scratch

Start a new App from an empty PostgreSQL database, a schema.sql and twenty-one lines of TypeScript.

You need Node 24 or newer and a PostgreSQL server you can create a database on. Already have a database? Draft the App from it.

In an empty folder:

Terminal window
npm init -y && npm pkg set type=module && npm install tablewalk typescript

You should now see added … packages and found 0 vulnerabilities. TypeScript is the project’s own: tablewalk check types the App with it, and with no other.

Terminal window
createdb billing

You should now see nothing: createdb is quiet when it works.

schema.sql
create table customer (
id serial primary key,
name text not null,
email text
);
create table invoice (
id serial primary key,
customer_id integer not null references customer (id),
total numeric(10, 2) not null,
paid boolean not null default false
);

Write a reference in full, references customer (id): the diff tool below reads a bare references customer wrongly on SQLite.

4. Name the database, and let the App write

Section titled “4. Name the database, and let the App write”
tablewalk.json
{
"connections": [{
"name": "billing",
"url": "postgres://localhost/billing",
"writable": true
}]
}

Writing is opt-in for each connection: without "writable": true the App only reads, whatever the App says.

app.ts
import { defineApp, listView } from 'tablewalk/app';
export default defineApp({
id: 'billing',
name: 'Billing',
connection: 'billing',
resources: {
customer: { access: 'write' },
invoice: { access: 'write' },
},
views: {
invoices: listView('Invoices', 'invoice', {
show: ['customer_id', 'total', 'paid'],
}),
customers: listView('Customers', 'customer', {
show: ['name', 'email'],
}),
},
nav: ['invoices', 'customers'],
home: 'invoices',
});

You should now see app.ts, package.json, schema.sql and tablewalk.json beside node_modules.

Terminal window
npx tablewalk migrate diff init --app .

You should now see wrote migrations/V1__init.sql: and the SQL it holds: the first migration is schema.sql itself, from create table customer to references customer (id), with nothing downloaded. Read it before it runs. The next migrate diff, after you change schema.sql, fetches the diff tool once (Atlas, about 40 MB) into .tablewalk/bin; add .tablewalk/ to .gitignore.

Terminal window
npx tablewalk migrate up --app .

You should now see applied V1__init.sql, Applied 1 migration: now at version 1., and then the App checked against the tables it made: typecheck passed and validation passed Billing: ok.

Terminal window
npx tablewalk --app .

You should now see Billing: http://localhost:4111/. Open it:

  • Invoices, empty (No rows match.), with New invoice; Customers beside it in the menu;
  • the New invoice form asks for a Customer and a Total, both required, and sets Paid with a switch that starts at No;
  • add a customer and its page opens at /customers/1, with its invoices listed under it.
  • App error: … "Billing" declares the tables "customer" and "invoice", which billing does not have → the migration is not applied yet: run step 7.

  • createdb: error: connection to server … failed → PostgreSQL is not running, or not on its default port; start it, or put the host, port and user in the url.

  • On SQLite, name a file in the url ("./billing.db") and skip createdb; the same schema.sql serves, its serial primary key read as SQLite’s integer primary key.