# Quickstart (/docs/using/quickstart)



<AlphaNotice />

This takes you from a running stack to a database your app reads and writes through the data API. It uses self-hosted mode and needs Docker (the worker starts each database's compute container).

## 1. Start the stack and sign in [#1-start-the-stack-and-sign-in]

1. Install Chelabase with Docker Compose by following [Install](/docs/self-hosting/install): `cp .env.example .env && docker compose up -d --wait` in `infra/compose`. (To work on Chelabase itself, `just workbench up` is the development loop instead; it is not how you run it.)
2. Read the setup code with `docker compose logs init-secrets`, open [http://studio.chelabase.localhost:8000](http://studio.chelabase.localhost:8000), choose **Sign in**, then **Create account**, and enter the code. The first account with the code claims the instance and becomes the admin. Don't expose the host before you have claimed it.
3. On `/app` you see your projects. Open your project to see its databases and a form to create one.

<Callout type="info">
  Self-sign-up is off by default: an admin invites everyone else (an email, or a link to hand over when no SMTP server is configured), and they join the same tenant, a shared team workspace. An admin can switch open sign-up on; people who then sign up wait for approval. Use the Studio host you configured (`http://studio.chelabase.localhost:8000` by default): the gateway's CORS settings and the sign-in session are set for that host. On a workbench stack, emails such as invites and password resets land in Mailpit at [http://mail.chelabase.localhost:8000](http://mail.chelabase.localhost:8000).
</Callout>

When sign-up is open, the sign-up's email verification follows `CHELABASE_REQUIRE_EMAIL_VERIFICATION` (`true` requires a code, which the identity service emails, so it needs SMTP; on a workbench stack the code lands in Mailpit; `false` turns it off); without it the default follows the mode: off when self-hosted, required in platform mode. Invitees verify their email by accepting the invite.

## 2. Create a database [#2-create-a-database]

On your project's page, enter a name in the form and create it. The database shows as provisioning while its Neon tenant, timeline and compute are made, then as ready. Studio polls every 2 seconds until the status settles.

`GET http://api.chelabase.localhost:8000/v1/databases/<id>` reports its `status` from a terminal.

## 3. Create a table [#3-create-a-table]

Choose **SQL** on the database and run, as the owner (the default for an admin):

```sql
create table todos (
  id bigint generated always as identity primary key,
  title text not null,
  done boolean not null default false
);
```

Tables the owner creates in the default schema are open to the data API's roles at once: `authenticated` (behind a secret key) may read and write them, `anon` (behind the public key) only read ([default privileges](/docs/architecture/data-api#default-privileges)). Studio warns that `todos` has no row-level security; add a policy before a public key reaches it from a browser.

## 4. Create a key [#4-create-a-key]

On the database, open **Data API**, then under **Secret keys** choose **Create key**. Give it a name and keep the role `authenticated`. The key (`chk_sec_…`) is shown once: save it. The same panel shows the branch's public key (`chk_pub_…`, role `anon`, safe in a browser). See [Keys](/docs/using/data-api/keys), including the API calls that do the same.

## 5. Query it from your app [#5-query-it-from-your-app]

`@chelabase/client` is the typed client for the data API. It is a workspace package in the repository (`packages/client`), not yet on npm: add it to a workspace app as `"@chelabase/client": "workspace:*"`.

Get the generated types of the branch's schema, `db.ts`: on the **Data API** view, choose **Download types** (keep the role `authenticated`, the key's). Or fetch the same file with the secret key, from a terminal or any build step:

```bash
export CHELABASE_DATA_KEY=chk_sec_…
curl -fsS -H "Authorization: Bearer $CHELABASE_DATA_KEY" \
  http://api.chelabase.localhost:8000/data/v1/types -o db.ts
```

Then write and read rows:

```ts
import { createClient } from "@chelabase/client";
import { type Database, types } from "./db";

const db = createClient<Database>({
  url: "http://api.chelabase.localhost:8000",
  key: process.env.CHELABASE_DATA_KEY!,
  types,
});

await db.from("todos").insert([{ title: "Try Chelabase" }]).returning("id");
const open = await db.from("todos").select("id", "title").where({ done: false });
```

A server can instead fetch the runtime types when it starts, so its value decoding follows the live schema without a rebuild (the `Database` type still comes from `db.ts`):

```ts
import { createClient, fetchTypes } from "@chelabase/client";
import type { Database } from "./db";

const url = "http://api.chelabase.localhost:8000";
const key = process.env.CHELABASE_DATA_KEY!;

const db = createClient<Database>({ url, key, types: await fetchTypes({ url, key }) });
```

A secret key stays on a server; in a browser use the public key, with row-level security on the tables it reaches. The [TypeScript client](/docs/using/client) covers filters, writes, functions, batches and [generated types](/docs/using/client/types); the [data API](/docs/using/data-api) is the HTTP endpoint underneath, for any other language:

```bash
curl -s http://api.chelabase.localhost:8000/data/v1/query \
  -H "Authorization: Bearer $CHELABASE_DATA_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "v": 1, "op": "select", "from": "todos", "limit": 10 }'
```

## Connect with a Postgres client [#connect-with-a-postgres-client]

Migrations, ORMs and admin tools can also connect to the database over the Postgres protocol, through the install's proxy. Open **Connection details** on the database in Studio, or call `GET /v1/databases/{id}/connection`. You get the owner's credentials and a URI such as:

```txt
postgresql://postgres:…@<endpoint>.localtest.me:5432/<db>?sslmode=verify-full
```

The proxy's certificate is issued by the install's own CA. Fetch it once, in `infra/compose`, to the file name the connection details give as `ca_file`:

```bash
docker compose cp proxy:/tls/ca.crt proxy-ca.crt
psql "<uri>&sslrootcert=proxy-ca.crt"
```

`localtest.me` resolves to the machine itself, where the proxy is published (`127.0.0.1:5432` by default). If it resolves to `::1` first and that fails, add `&hostaddr=127.0.0.1`. For another host, port or a real domain, see [Install: connect to databases](/docs/self-hosting/install#connect-to-databases). On a workbench stack, `just workbench status` shows the proxy's port (4432 for the main stack), and the CA is at `.chelabase/stacks/<stack>/neon/proxy-ca.crt`. You can show the connection details as often as you like.

## 6. Remove it [#6-remove-it]

Delete the database in Studio (it asks you to type the name) or with `DELETE /v1/databases/{id}`. On a workbench stack, `just sweep-dbs` removes every database.

## Next [#next]

* Query the database from your app with the [data API](/docs/using/data-api) and its [TypeScript client](/docs/using/client).
* Bring in your team with [members and invites](/docs/using/members-and-invites), and scope what each person can reach with [roles and schema isolation](/docs/using/roles-and-schema-isolation).
* Learn how [projects and databases](/docs/using/projects-and-databases) and [branches](/docs/using/branches) work.
