Kitten

Learn how to persist information using Kitten’s built-in JavaScript Database (JSDB)

Topics covered

Persistence (is not) futile!

So counting kittens is great fun but what happens if you restart the server?

All your kittens are lost! (This is a tragedy.)

So let’s fix that.

(Brace yourself, you’re about to use – drumroll – a SCARY database! Oooh!)

šŸ‘» Using JavaScript Database (JSDB) – a (not so) scary database

Update the code in our running Kitten Count example to match this:

kitten.db.kittens ??= { count: 1 }

export default function () {
  return kitten.html`
    <h1>Kitten count</h1>
    <p>${'šŸ±ļø'.repeat(kitten.db.kittens.count++)}</p>
  `
}

Your page should automatically reload in the browser with the new count.

Now refresh the page a few times, manually stop the server, restart it, and load the page again…

Wait, what?

That’s it?

Seriously?

Yep, that’s the magic of the integrated JavaScript Database (JSDB) in Kitten.

If you don’t believe me, restart the server and note that all your kittens are still there.

If you still don’t believe me (wow, what a cynic), look in your Kitten app’s data folder. You can find a clickable link to it in the welcome message output into your terminal by Kitten when it first starts.

Screenshot (detail) of Kitten’s initial output with the link to open the data folder highlighted.
Kitten’s welcome screen has handy clickable links to the URL, source code, and data folder of your project.

In the screenshot, above, since the Kitten app being served is located at ~/Projects/kitten/app/examples/persisted-kitten-count and being served at localhost, port 443, the data folder can be found at:

~/.local/share/small-tech.org/kitten/data/Users.aral.Projects.kitten.app.examples.persisted-kitten-count.localhost.443

You can also find your app’s data folder path in Kitten’s interactive shell (REPL) by referencing the kitten.paths.APP_DATA_DIRECTORY property.

Inside your app’s data folder, the db folder contains a kittens.js file.

Open it up in a text editor and take a look at it.

It should look something like this:

export const _ = { [`count`]: 40 };
_[[`count`]] = 41;
_[[`count`]] = 42;

That’s what a table looks like in JavaScript Database (JSDB), the database that’s integrated into Kitten and available from all your routes via the global kitten.db reference.

While looking at the actual persisted database table files gives you the full history of the JavaScript append-only log that Kitten persists your data as, if you want to see just the current state of a database object, you can use Kitten’s interactive shell (REPL).

e.g., To see the state of the persisted kittens object, start Kitten’s interactive shell by pressing s in the terminal window where Kitten is running and then enter the following statement:

kitten.db.kittens

You should see output similar to that in the following screenshot.

Output of running kitten.db.kittens in Kitten’s interactive shell (REPL) in terminal: Proxy { count: 10 }, …rest of object}
You can query the current state of a persisted object in Kitten’s JSDB database using Kitten’s interactive shell (REPL).

You’re not limited to seeing a static snapshot of your database’s state, you can also follow it in real time using the Kitten Introspection API.

e.g., To see the events stream to the console as you increase the kitten counter, enter the following in the REPL:

kitten.$showEventsOnTable('kittens')

Kitten db command

Kitten also provides you with a simple command-line command to get information about and see a live view of your tables.

To get general information about the database, including where the database is located and which tables it has, use the db command from your project’s folder:

kitten db

If you want to see information about a specific table (including the path of its file), add its name to the db command. To see a live view of the contents of a table, use the db tail command.

So for the Persisted Kitten Count example, if you wanted to see a live view of the kittens table, you would enter:

kitten db tail kittens

You are not limited to storing plain objects in your database. You can also store custom objects (instances of custom classes) and get them back with the correct type when you read them. That’s a more advanced feature, however, and you will need to implement a database app module to do it.

See the Database App Modules tutorial for an example of advanced database use.

There’s so much more to JSDB and you can learn all about in the JSDB documentation.

Initialising things

If initialising your database table in your route feels a little yucky, that’s because it is. What if more than one route needed to use that table? If we weren’t absolutely certain that the routes would be called in a given order, we’d have to repeat the conditional initialisation in every route just to be safe.

You can see how this could become a maintenance nightmare.

Fear not, Kitten to the rescue!

If you create a default entrypoint – a special script called main.script.js in the root of your project folder – and export a default function from it, Kitten will import that function and run it at start up.

This default entrypoint is a great place to carry out global initialisation for your app.

Kitten also calls this function with a parameter object that has a property called router, which you use to add routes and middleware to the Polka app for advanced tasks, if you need to. Routes you add through router come after Kitten’s own routes and before your app’s file routes. (A route you add at the same method and path as one of your route files stops Kitten at start-up.)

Remember that middleware you add with router.use runs before Kitten parses the request body, so request.body is empty there. Middleware reads headers; routes read bodies.

For our purposes, we could move the conditional initialisation of our database table to main.script.js like this:

export default function () {
 kitten.db.kittens ??= { count: 1 }
}

Which would leave our route looking rather pristine:

export default function () {
  return kitten.html`
    <h1>Kitten count</h1>
    <p>${'šŸ±ļø'.repeat(kitten.db.kittens.count++)}</p>
  `
}

There is another way to achieve this that’s specifically designed for the purpose of initialising databases (and adding strong typing to them) using a special App Module called a Database App Module.

Right, so now that we know that persisting things isn’t scary in Kitten, let’s take another aside and look at one of Kitten’s nifty features: its interactive shell (REPL). And, in the process, we’ll also learn more about Kitten’s JSDB database and take a first look at how components and fragments work in Kitten.

Next tutorial: Kitten’s interactive shell (REPL)

Like this? Fund us!

Small Technology Foundation is a tiny, independent not-for-profit.

We exist in part thanks to patronage by people like you. If you share our vision and want to support our work, please become a patron or donate to us today and help us continue to exist.