Files
trailbase/examples/data-cli-tutorial/README.md
2025-05-19 14:28:19 +02:00

153 lines
5.4 KiB
Markdown

# TrailBase Tutorial
In this tutorial, we'll set up a database with an IMDB test dataset, spin up
TrailBase and write a small program to access the data.
In an effort to demonstrate TrailBase's loose coupling and the possibility of
simply trying out TrailBase with an existing SQLite-based data analysis
project, we will also offer a alternative path to bootstrapping the database
using the vanilla `sqlite3` CLI.
## Create the Schema
By simply starting TrailBase, the migrations in `traildepot/migrations` will be
applied, including `U1728810800__create_table_movies.sql`:
```sql
CREATE TABLE IF NOT EXISTS movies (
rank INTEGER PRIMARY KEY,
name TEXT NOT NULL,
year ANY NOT NULL,
watch_time INTEGER NOT NULL,
rating REAL NOT NULL,
metascore ANY,
gross ANY,
votes TEXT NOT NULL,
description TEXT NOT NULL
) STRICT;
```
Note that the only schema requirement for exposing an API is: `STRICT` typing
and an integer (or UUIDv7) primary key column.
The main benefit of relying on TrailBase to apply the above schema as migrations
over manually applying the schema yourself, is to:
* document your database's schema alongside your code and
* even more importantly, letting TrailBase bootstrap from scratch and
sync-up databases across your dev setup, your colleague's, every time
integration tests run, QA stages, and in production.
That said, TrailBase will happily work on existing datasets, in which
case it is your responsibility to provide a SQLite database file that
meets expectations expressed as configured TrailBase API endpoints.
Feel free to run:
```bash
$ mkdir traildepot/data
$ sqlite3 traildepot/data/main.db < traildepot/migrations/U1728810800__create_table_movies.sql
```
before starting TrailBase the first time, if you prefer bootstrapping the
database yourself.
## Importing the Data
After creating the schema above, either manually or starting TrailBase to apply
migrations, we're ready to import the IMDB test dataset.
We could now expose an API endpoint and write a small program to first read the
CSV file to then write movie database records... and we'll do that in a little
later.
For now, let's start by harnessing the fact that SQLite databases are simply a
local file and import the data using the `sqlite3` CLI side-stepping TrailBase:
```
$ sqlite3 traildepot/data/main.db
sqlite> .mode csv
sqlite> .import ./data/Top_1000_IMDb_movies_New_version.csv movies
```
There will be a warning for the first line of the CSV, which contains textual
table headers rather than data matching our schema. That's expected.
We can validate that we successfully imported 1000 movies by running:
```sql
sqlite> SELECT COUNT(*) FROM movies;
1000
```
## Accessing the Data
With TrailBase up and running (`trail run`), the easiest way to explore your
data is go to the admin dashboard under
[http://localhost:4000](http://localhost:4000)
and log in with the admin credentials provided to you in the terminal upon
first start (you can also use the `trail` CLI to reset the password `trail user
reset-password admin@localhost`).
In this tutorial we want to explore more programmatic access and using
TrailBase record APIs.
```textproto
record_apis: [
# ...
{
name: "movies"
table_name: "movies"
acl_world: [READ]
acl_authenticated: [CREATE, READ, UPDATE, DELETE]
}
]
```
By adding the above snippet to your configuration (which is already the case
for the checked-in configuration) you expose a world-readable API. We're using
the config here but you can also configure the API using the admin dashboard
via the
[tables view](http://localhost:4000/_/admin/tables?pageIndex=0&pageSize=20&table=movies)
and the "Record API" settings in the top right.
Let's try it out by querying the top-3 ranked movies with less than 120min
watch time:
```bash
curl -g 'localhost:4000/api/records/v1/movies?limit=3&order=rank&filter[watch_time][$lt]=120'
```
You can also use your browser. Either way, you should see some JSON output with
the respective movies.
## Type-safe APIs and Mutations
Finally, let's authenticate and use privileged APIs to first delete all movies
and then add them pack using type-safe APIs rather than `sqlite3`.
Let's first create the JSON Schema type definitions from the database schema we
added above. Note, that the type definition for creation, reading, and updating
are all different. Creating a new record requires values for all `NOT NULL`
columns w/o a default value, while reads guarantees values for all `NOT NULL`
columns, and updates only require values for columns that are being updated.
In this tutorial we'll "cheat" by using the same type definition for reading
existing and creating new records, since our schema doesn't define any default
values (except implicitly for the primary key), they're almost identical.
In preparation for deleting and re-adding the movies, let's run:
```bash
$ trail schema movies --mode insert
```
This will output a standard JSON schema type definition file. There's quite a few
code-generators you can use to generate bindings for your favorite language.
For this example we'll use *quicktype* to generate *TypeScript* definitions,
which also happens to support some other ~25 languages. You can install it, but
for the tutorial we'll stick with the [browser](https://app.quicktype.io/)
version and copy&paste the JSON schema from above.
With the generated types, we can use the TrailBase TypeScript client to write
the following program:
```ts
# scripts/src/fill.ts
```