Reference
The reference section is under development and should in no way, shape, or form, be considered to be exhaustive at the moment.
This reference guide lists the features of Kitten. For a comprehensive โownerโs manualโ-style technical reference of how Kitten is designed and works internally, please see the Technical Manual.
About
Kitten is a little kit (โkittenโ, get it?) optimised for people (not corporations) who make the new everyday things for themselves and other people โ not because they want to become billionaires but because they want to contribute to the common good by helping nurture the Small Web while adhering to the Small Technology Principles.
Kitten features intuitive file system-based routing, HTML and accessibility validation, a built-in object database, WebSockets, zero-code authentication, public-key cryptography, Markdown support, syntax highlighting via highlight.js, semantic styles via Water CSS, and more.
System requirements
- Linux and macOS.
- Bash version 5.x+.
- Common developer tools and system utilities (
git,tar,tee, andxz).
macOS comes with an ancient version of Bash. To upgrade it to a modern one, run brew install bash using Homebrew.
For production servers, only Linux with systemd is supported.
Installing Kitten
To install Kitten, either:
๐ Clone the Kitten repository and run the
./installscript:git clone https://codeberg.org/kitten/app kitten cd kitten ./install๐ Or, copy and paste one of the following commands into your terminal to pipe the installation script into your shell and run it:
Using wget:
wget -qO- https://kittens.small-web.org/install | bashUsing curl:
curl -sL https://kittens.small-web.org/install | bashDownload and view the install script source to verify itโs safe to pipe to your shell.
You can also compare it to the code in the source repository to ensure theyโre the same.
Post-installation steps (macOS)
On macOS, you have a couple of manual post-installation steps to perform.
(Detailed instructions on how to carry out the below steps are presented in the installer at installation time.)
You must update your systemโs path with the location of the kitten binary as ~/.local/bin is not on the path by default on macOS.
(You will also have to do this on Linux distributions that donโt automatically add this folder to your system path. The installer will prompt you to do this if it detects that this is the case.)
If you want to test peer-to-peer apps locally, you must edit your ~/etc/hosts file to add Kittenโs localhost aliases (place1.localhost, place2.localhost, โฆ, place4.localhost).
To learn more about building peer-to-peer Small Web apps, see the End-to-end encrypted peer-to-peer Small Web apps tutorial.
If you want to install not the latest version of Kitten but the latest version for a given API version, modify your download command to tack the api version to the URL and to pass it as an argument to the bash script. For example, if you wanted to install the latest Kitten package for API version 1 using CURL, your command would look like this:
curl -sL https://kittens.small-web.org/install/1/ | bash -s -- 1If youโre running an โimmutableโ Linux distribution like Fedora Silverblue, please install Kitten from your host account (instead of from within a container) at least once so Kitten can set unprivileged ports to start from 80 (so it can run without elevated privileges).
Updating Kitten
You can update Kitten in a variety of ways:
If you use the command-line during development, run the
kitten updatecommand.If you want to manually update Kitten in a deployed app, you can use the web interface on the Kitten Settings page (/๐ฑ/settings/kitten/).
If you have a deployed app that uses Kitten Versioning, both Kitten and the installed app will be automatically updated. These updates will ensure that Kitten is not updated if the installed app itself has not been updated to support the latest Kitten API version to ensure that deployed apps do not break due to breaking changes in major API version of Kitten.
Note that this API version guarantee will only come into effect after Kitten reaches API version 1. Kitten is currently in pre-release at API version 0.
Authoring
HTML
You work with HTML in Kitten using Kittenโs html tagged template string format that is exposed at kitten.html.
e.g., Hereโs a simple page route that prints โHello, world!โ:
export default function () {
return kitten.html`
<h1>Hello, world!</h1>
`
}
Put the above code into a file called index.page.js, run kitten in that directory, and navigate to https://localhost in your browser to see it work.
To learn more about Kitten HTML, see the Dynamic Pages tutorial.
Kitten has a semi-forgiving HTML parser.
XML-style closing tags are not required for regular HTML tags.
For example, you can use
<img>instead of<img />.For custom Kitten components, you must close your tags.
So, for example:
- Valid:
<${MyComponent} /> - Valid:
<${MyComponent}>Slotted content๎ผ</div> - Invalid:
<${MyComponent}>
- Valid:
The parser will make a best-effort to close unclosed tags.
So, for example, it will replace
<p>Hellowith<p>Hello</p>but itโs not foolproof.You will get an unexpected close tag error if you put flow content where phrasing content is expected (e.g., a
<div>inside a<p>tag, etc.)Comments in HTML are stripped from the final output.
Thereโs currently no way to include comments in the markup generated by Kitten.
The page tag
Youโll notice in the Hello, world! example that we just returned a <h1> tag instead of the full HTML structure for the page.
Kitten has a default template that creates the boilerplate of an HTML page for you (the whole structure, starting with <!doctype html><html lang='โฆ'><head>โฆ, etc.)
To add to parts of this template you can use two mechanisms:
- The
<page>tag - Special page slots
Special page slots take precedence over and override <page> tag settings.
The page tag takes the following attributes:
HTML attributes
These customise the <html> tag:
lang(defaulten)
Head attributes
These customise the <head> tag:
title: sets contents of<title>tag (default: automatically set based on first heading in page, otherwise empty)charset: sets contents of<meta charset='โฆ'>tag (default:UTF-8)viewport: sets contents of<meta name='viewport' content='โฆ'>tag (default:width=device-width, initial-scale=1.0)icon: sets contents of<link rel='icon' href='โฆ'>(default:data:,)
Body attributes
These customise the <body> tag:
bodyClass: setsclass=โฆon body (default:'')
Libray includes
These attributes trigger library includes:
htmx: includes Kittenโs internal version of htmxidiomorph: includes Kittenโs internal version of the htmx idiomorph extensionws: includes Kittenโs internal version of the htmx WebSocket extensionalpine: includes Kittenโs internal version of Alpine.jscss(includes the Water semantic CSS library)
Libraries like htmx, idiomorph, etc., are automatically added to the page when you use the connect attribute on Kitten components, export event handlers from the your Kitten pages or make your pages into KittenPage subclasses. In other words, in daily usage, you should not have to specify the htmx, idiomorph, or ws tags manually.
Other attributes
These attributes customise other Kitten-specific behaviour:
syntax-highlighter: turns on syntax highlighting for the page (includes the default syntax highlighting themes โ see below โ that are applied to any code blocks marked up in HighlightJS. Kittenโs Markdown parser automatically generates code fences with HighlighJS markup).syntax-highlighter-theme-light: sets thehrefof the lightmode Highlight.js stylesheet to use. (default: โ/๐ฑ/library/highlight.js/styles/measured-light.cssโ)syntax-highlighter-theme-dark: sets thehrefof the dark mode Highlight.js stylesheet to use (default: โ/๐ฑ/library/highlight.js/styles/measured-dark.cssโ)
See the Syntax Highlighting section for more details.
Request and response helpers
Kitten has a number of request and response helpers defined to make your life easier.
You use two of them, response.forbidden() and response.get() in the authentication tutorial.
Request
is (array|string): returns true/false based on whether thecontent-typeof the request matches the string or array of strings presented.This is used internally for Express Busboy compatibility but you might find it useful in your apps too if youโre doing low-level request handling.
Response
withCode(code, [body]): Shorthand for setting.statusCodeonresponseand callingresponse.end()with an optional body. Useful when returning custom HTTP Status Codes like the ones used in the Small Web Protocol.ok(): Returns 200 OK.created(): Returns 201 Created.json (data): Returns a JSON serialised response of the passed data.jsonFile (data): Triggers a download of the passed data in JSON format.file (data, fileName?, mimeType?): Sends downloadable file (default name isdownloadand default MIME type isapplication/octet-stream).get (location),seeOther (location): 303 See Other redirect (always uses GET).redirect (location),temporaryRedirect (location): 307 Temporary Redirect (does not change the request method).permanentRedirect (location): 308 Permanent Redirect (does not change the request method)badRequest (body?): 400 Bad Request.unathenticated (body?),unauthorised (body?),unauthorized (body?): 401 Unauthorized (unauthenticated).forbidden (body?): 403 Forbidden (request is authenticated but lacks sufficient rights โ i.e., authorisation โ to access the resource).notFound (body?): 404 Not Found.error (body?),internalServerError (body?): 500 Internal Server Error.
Streaming HTML
Kitten gives you a simple-to-use event-based HTML over WebSocket implementation called Streaming HTML (because youโre streaming HTML updates to the client) that you can use to build web apps.
See the Streaming HTML tutorial for details.
Kitten components
Simple components
A Kitten component is any function that returns kitten.html.
So this can be as simple as:
const SayHello = () => kitten.html`
<h1>Hello</h1>
`
Of course, static components are not very useful, so component functions usually take a parameter object with properties (or โpropsโ, for short):
const SayAnything = ({ thingToSay = 'Hello' } = {}) => kitten.html`
<h1>${thingToSay}</h1>
`
console.log(kitten.html`<${SayAnything} thingToSay='Goodbye' />`
The above example, for example, will output:
<h1>Goodbye</h1>
To learn more about Kitten components, see the following tutorials:
kitten.Component components
While simple components are great, if you use Kittenโs Streaming HTML workflow, you may find yourself having to do a bit of manual work if you want to use simple components and wire them up to listen for events from connected pages. This will become especially true if you carry out precise updates, morphing small bits of your interface in response to events and do so in a maintainable manner by breaking up your interface into small, well-encapsulated components.
To make this easier for you, Kitten provides the Component class that you can reference in your apps from the global kitten namespace.
Remember that you can get language intelligence and strong typing for your apps by importing the type safe version of the kitten global from the @small-web/kitten module.
For a real-world example of the use of the kitten.Component class โ and its page-level equivalent, kitten.Page โ see the code for the Internal Database page of Kittenโs of own Settings app.
Icons

Kitten includes a subset of the Phosphor icon set by Helena Zhang and Tobias Fried as Kitten components.
The main difference with the original set is that some icons have been removed and some have been renamed to bring the library in line with the Small Technology Principles.
Specifically:
All brand icons for Big Tech/Silicon Valley/surveillance capitalists/people farmers have been removed.
The words โuserโ and โusersโ have been replaced with โpersonโ and โpeopleโ. (In Small Tech and on the Small Web we donโt have users, we have people, and we treat them as such.)
e.g., The Phosphor icon called
user-checkexists as a Kitten component atkitten.icons.PersonCheck. The icon โuserโ has been renamed to โperson-close-upโ (as there is already a โpersonโ icon) and may be found atkitten.icons.PersonCloseUp.The BTC icon has been removed (proof of work; environmental concerns).
Finding icons
In your Kitten app, you can access the icons as simple Kitten components at the following namespace:
kitten.icons
In that namespace, you browse the icons in three ways:
- Directly, by name, e.g.,
kitten.icons.Cat - In categories, e.g.,
kitten.icons.categories.nature.Cat - Using tags, e.g.,
kitten.icons.tags.kitten.Cat
The categories and tags objects are authoring-time aids. While you can use them in your code, itโs better practice to reference the icons directly from the root namespace. These two properties are marked as unenumerable so they do not pollute the namespace and will not show up if youโre iterating over all the icons in the root namespace.
To get autocompletion in your editor, your editor must support the TypeScript language server (any modern code editor worth using will e.g., Helix editor, etc.) and you must install and use the type-safe Kitten globals module:
import kitten from '@small-web/kitten'
kitten.icons.categories. // Will show a pop-up with list of categories in your editor.
Using icon components
You can use the components in your kitten.html as you would any other Kitten component.
e.g., A simple Kitten page that displays a large, pink cat icon in duotone:
export default function () {
return kitten.html`
<${kitten.icons.Cat}
size=40%
weight=duotone
colour=deeppink
/>
`
}
As the icons are Kitten components, you can customise how they appear by passing properties (โpropsโ) to them:
alt: Alternative text (alt-text). Provide this to explain the purpose of the icon for people who use screen readers. If no alt text is provided, it will default to the name of the icon.colour: Pass a CSS colour. Defaults tocurrentColor, which will pick up the colour of its context.weight: One ofregular,light,thin,fill, orduotone. Defaults toregular.size: Pass a CSS measurement to use as the width/height (the icons are square). Defaults to1em.mirror: Pass a boolean. Mirroring can be useful in right-to-left layouts. Defaults tofalse.
And other props you provide will be passed as attributes to the SVG tag of the icon.
Extending icon components
You can extend the SVG (e.g., to add animation, etc.) by slotting any valid SVG content into it.
e.g., This makes the cat into a Cheshire cat (fades in and out):
export default function () {
return kitten.html`
<${kitten.icons.Cat}
size=40%
weight=duotone
colour=deeppink
>
<animate
attributeName="opacity"
values="0;1;0"
dur="3s"
repeatCount="indefinite"
/>
</>
`
}
While the above example is good for a one-time use modification, you can, of course also make a reusable CheshireCat component.
Hereโs one that has all the same defaults as the one in the above example but is fully configurable via props.
Notice how it passes any other props it receives to the base component itโs extending via composition.
const CheshireCat = ({duration = 3, minOpacity = 0, maxOpacity = 1, ...props}) => {
return kitten.html`
<${kitten.icons.Cat} ...${props}>
<animate
attributeName="opacity"
values="${minOpacity};${maxOpacity};${minOpacity}"
dur="${duration}s"
repeatCount="indefinite"
/>
๎ผ</div>
`
}
export default function () {
return kitten.html`
<${CheshireCat}
size=40%
weight=duotone
colour=deeppink
/>
`
}
Displaying all icons
Hereโs a simple example that displays all the icons, separated alphabetically, with their component names appearing in a tooltip when you hover over them.
You can find a screenshot of it at the top of this section.
export default function () {
return kitten.html`
<page css>
<markdown>
# All icons
These are all the icons in the ```kitten.icons``` namespace.
</markdown>
${Object.entries(kitten.icons).map(([name, Icon]) => kitten.html`
<${Icon} size=2em weight=duotone colour=deeppink>
<title>${name}</title>
</>
`)}
`
}
Markdown support
Kittenโs Markdown support comes from markdown-it.
Kitten supports a very rich set of Markdown features including:
typographical niceties (like converting typewriter quotes โโ into curly quotes โโ, etc.)
adding classes, identifiers and attributes to your markdown
e.g.,
# Classy heading {.myClass #identifier attribute=value attribute2="spaced value"}e.g.,
This is my [classy span]{.myClass} and I love it!e.g.,
Inline images  are rendered normally, using just an `<img>` tag. ')e.g.,
Inline footnotes rock.^[Inline footnote are easy to write and update.]
When writing Markdown, please indent the contents of your code fences to follow the natural indentation rhythm of your page.
Specifically, do not pull them flush left in an effort to remove trailing whitespace. Kitten does this for you, intelligently, when creating your preformatted code areas in HTML.
If you do pull the code fence contents outside of the natural indentation rhythm of the page, you will get a render error.
There is a bug in the CommonMark spec that results in preformatted code with empty lines nested in div not rendering correctly:https://github.com/commonmark/commonmark-spec/issues/807
In Kitten, this throws an error.
See test: https://codeberg.org/kitten/app/src/branch/improved-markdown-pages/tests/markdown.js#L9
This is a bug with the CommonMark spec/parser and affects libraries like markdown-it (which Kitten uses) that are based on it.
You can use Markdown in your Kitten apps in a variety of ways:
Markdown pages (.page.md files).
You can create whole pages using Markdown by placing your Markdown in page.md files. These pages are transpiled into JavaScript before being transpiled into HTML just like regular JavaScript pages (page.js) files.
Your Markdown pages can have front matter in YAML format.
Kittenโs YAML parser is based on the Node yaml module and you can use it directly in your own Kitten apps by referencing it from kitten.yaml.
Front matter
Kittenโs YAML front matter supports some special properties. Specifically:
script (literal block scalar)
You can include arbitrary JavaScript in a script block.
For example, hereโs the Kitten Count example as a Markdown page with a script block:
---
script: |
let count = 1
---
# Kitten count
${'๐ฑ๏ธ'.repeat(count++)}
As this example shows, you can use JavaScript string interpolation inside of Markdown pages just as you would in a regular JavaScript file.
layout (string)
Specifies the layout template to use for the Markdown page.
The value for the layout property is a string that contains the relative path to the layout template (.layout.js file) you want to import and use.
Any custom properties in front matter are passed to the layout template as properties.
For example, if your layout template is called Main.layout.js and it is located in the layouts/ folder of your project and you want to include it in a page called index.page.md at the root of your project, your front matter might look like this:
---
layout: './layouts/Main.layout.js'
title: 'Feed me!'
author: 'Oskar Kalbag'
---
Woof.
And your layout template might look like this:
export default function MainLayout ({ SLOT, title, author } = {}) {
return kitten.html`
<header>
<h1>Oskarโs blog</h1>
<p>I am a dog.</p>
</header>
<main>
<h2>${title} by ${author}</h2>
${SLOT}
</main>
<footer>
<p>PS. Have you fed me?</p>
</footer>
`
}
The title and author fields you see in the example above are custom properties the author wants passed to the layout template.
kittenPage (true or false; default is false)
Determines whether the resulting page exports a simple page render function (when kittenPage is not provided or when it is set to false) or a Kitten.Page subclass.
Setting kittenPage: true in your front matter will enable you to use the Streaming HTML workflow with any components you attach to your page (as you will be able to author event handlers on them and, due to the Kitten Component hierarchy and automatic event bubbling, their handlers will get called).
Since your markdown pages are markdown and not JavaScript, they cannot export any event handlers of their own.
Custom properties in front matter
Any properties other than the special ones listed above are passed to your pageโs layout template (if it has one) as props.
In addition to any custom properties you may have, three built-in properties are always passed to the layout template by Kitten:
page: reference to the current page, if this is a live/connected page (KittenPage instance.)request: reference to the KittenRequest object. (A Node.js http.IncomingMessage object as as extended by Polka and then again by Kitten. The important Kitten addition is thesessionproperty that refers to the current session.)response: reference to the KittenResponse object. (A Node.js http.ServerResponse object as extended by Kitten. Kitten mixes in a number of useful response helpers.
When using JavaScript-based live pages, you can get references to the page from this.page and to the request and response objects via this.page.request and this.page.response from any connected component. However, since Markdown pages are not live pages, Kitten passes the references for you and you have no way of passing them to your layout component yourself in Markdown.
Markdown fragments
You can place static pieces of Markdown into Markdown fragment files (fragment.md).
Unlike Markdown pages, Markdown fragments do not support front matter or JavaScript string interpolation. However, you can use slots (both default and named) to inject values into them.
Markdown in Kitten HTML tagged template strings
Finally, you can embed Markdown directly in your Kitten HTML tagged template string by surrounding your Markdown content between <markdown>โฆ</markdown> tags.
e.g.,
export default () => kitten.html`
<h1>This is HTML</h1>
<markdown>
- _This_
- is
- __Markdown!__
</markdown>
<footer>
<p>This is more HTML</p>
</footer>
`
Unlike HTML, Markdown is whitespace-sensitive so make sure you indent your code properly or it will get parsed incorrectly.
You can see some of the Markdown features mentioned here demonstrated in the examples/components and examples/markdown folders of the Kitten examples, as well as in the source for the Kitten web site itself.
Syntax highlighting
Syntax highlighting in Markdown is provided via Highlight.js.
To turn on syntax highlighting for your page, include a <page> tag in your file that includes the syntax-highlighter attribute:
<page syntax-highlighter>
The default themes for light and dark mode are the Measured Light and Measured Dark accessible syntax highlighting colour scheme implementations for Highlight.js.
You can override this setting by setting the syntax-highlighter-theme-light and syntax-highlighter-theme-dark attributes of your <page> tag to the absolute or relative path to the respective theme CSS file.
View the full list of default Highlight.js themes.
See the <page> tag reference for more details.
Symlinks
If you have symlinks with extensions that Kitten knows how to route, your symlinks will get routed as if they were regular files.
e.g., You have a README.md file and you want the index of your page to display it without redundancy. Create a symlink inside your project folder:
ls -s ./README.md index.page.md
Now, when you hit the index of your site, you will see the README file.
Kitten will only route symlinks that point to files in your project directory or to folders under it, not outside. (Otherwise, it would open the door to a whole family of potential security issues.)
Deploying a Kitten application
To deploy your app as a service, you use the deploy command from a regular account (not root) under Linux:
kitten deploy <httpsGitCloneURL>
For example, the following will create and run a production server of the Persisted Kitten Chat example, as hosted on my personal Codeberg account at my hostname.
kitten deploy https://codeberg.org/aral/persisted-kitten-chat
Then, as Kitten runs in an unprivileged account, you have to tell systemd to let services for that account remain running when you sign out of your server (otherwise your Kitten server will stop when you exit your ssh session or log out of your server).
e.g., If you installed Kitten under a Linux account called kitten:
loginctl enable-linger kitten
Note that the name you use in the enable-linger command is the name of the Linux account, not the Kitten process/systemd unit file name. If you deployed a Kitten app from a Linux account called aral, you would instead type loginctl enable-linger aral.
Kitten will not automatically create an alias for www for you, pass --aliases=www.<hostname> (or the shorthand, --aliases=www) if you want that. You can, of course, also list any other subdomains other that www that you want Kitten to serve your site/app on in addition to the main domain.
If your app uses node modules, Kitten will intelligently call npm install on your project, as well as on any app modules your project might have.
Updating a deployed Kitten application
There are a number of ways you may wish to update your deployed Kitten application, depending on who the audience for your app or site is.
If youโre making a Small Web app that you envision being used by everyday people who use technology as an everyday thing, then you will want to implement Kitten versioning in your Git source code repositories.
If you use Kitten versioning in your apps, Kitten periodically and automatically checks for updates to your deployed app and makes sure your deployment is up-to-date.
If you are building something for yourself and youโre happy to manually update the site, you can do so from the App Settings page of your Kitten site (/๐ฑ/settings/app/).
If youโre building something for yourself and you want your Kitten site to update every time you push to the main branch of your Git repository, you can set up a webhook.
๐ชWebhook
You can set up a webhook on your remote Git source code repository that updates the site whenever you perform a certain git action (e.g., push to main).
You can find your webhook URL and webhook secret on the App Settings page of your Kitten site (/๐ฑ/settings/app/).
If your domain changes (i.e., if you point a new domain to your site using Kittenโs Settings panel), remember to update your webhook at your git host to use your new Webhook URL.
Your Kitten site expects the webhook secret in either the Authorization header or in the body of a POST request in a field called secret (the body should be application/x-www-form-urlencoded).
e.g., Instructions for Codeberg:
- From your projectโs repository, follow the Settings link.
- In the Settings menu, select Webhooks.
- Press the Add webhook button.
- From the buttonโs pop-up menu, select Forgejo.
- Enter the Webhook URL from below into the Target URL field.
- Leave the HTTP method set to
POST. - Set the POST content type to
application/x-www-form-urlencoded. - Leave the Secret field empty.
- Set Trigger on to
Push events - Set Branch filter to
main(or set to the branch you want to deploy from) - Enter the Webhook secret from below into the Authorization header field.
- Leave the Active checkbox checked.
- Press the Add webhook button.
Once the webhook has been successfully added, your site should update whenever you git push to main.
This is a feature that will be useful to developers who are deploying their own sites and apps for their own use as opposed to developers making apps for use by everyday people who use technology as an everyday thing. For the latter use case, please see Versioning, below.
Versioning
If you want to quickly test out a Kitten app that doesnโt have versioning information, use the kitten run command instead.
You version your Small Web apps using git tags:
Tag your Small Web apps in the following format to version them:
<Kitten API Version>.<App Version>
Where:
Kitten API Versionis the version of the Kitten API that your app is compatible with.App Versionis the version of your app, a monotonically increasing integer that starts at1.
To tag your app, simply use the git tag command as usual, e.g.,
git tag -s 1.3
The above tag will label your current commit as being compatible with Kitten API Version 1 and having an app version of 3.
If a deployed version of your app is running version 1.2, it will not be updated when you push commits to your main branch, etc. It will also not update the installed version of Kitten if, say, a new Kitten package is released that has an API Version of 2. Kitten will only be upgraded to the latest version when you release a version of your app thatโs tagged with support for Kitten API Version 2. e.g.,
git tag -s 2.1
Automatic updates of Small Web places and of Kitten itself follows one of the core tenets of the Small Web which is that Small Web places should not require technical knowledge to maintain.
As such, updates should never a break a Small Web place and require technical intervention to fix.
This means that your Small Web apps must never break backwards compatibility. So, for example, if you have schema changes in your database, you must write migration code to give existing data the shape that the latest version of your code requires, etc.
Kitten production servers periodically and automatically check for updates to both Kitten itself and to the Kitten app that is being served. Whenever there is a breaking change to the Kitten API (e.g., an existing method is removed or how itโs used is changed in such a manner that it could break existing code), the change is documented in the change log and the Kitten API version, which is a monotonically increasing integer, is incremented by one.
Automatic versioning in production
To understand how automatic versioning in production works, imagine that you deploy a new server running a Kitten version that has its API version set to 1. You specify this API version in your git tag as shown above. Whenever Kitten checks for Kitten updates, it ensures that the API version is still 1 before updating Kitten.
Say that after three Kitten updates, Kitten notices that the latest version of Kitten has an API version of 2 but that your app (which Kitten is also periodically checking for updates for from its git repository) is still at API version 1. At that point, Kitten does not install the latest version of Kitten but rather waits for you to test your app with Kitten API version 2 and specify that it is supported by creating a new git tag accordingly.
If there are any other Kitten packages released that support Kitten API version 1 (e.g., bug/security fixes that are backported from the latest versions of Kitten), Kitten will continue to update itself to those versions.
Once Kitten sees that youโve updated your app to Kitten API version 2, it will go ahead and update itself to the latest package supported by that version.
So, all this to say that Kitten tries its hardest to ensure that its auto-updates system does not break your servers.
If you are using a database in your app, please remember that you are responsible for ensuring that schema changes do not break your app between versions. In other words, you are responsible for implementing database migrations.
To make this easier on yourself, you might want to ensure that the collections and individual data items you persist are custom classes that abstract away the actual objects that are persisted. That way, you can implement granular migrations in your model classes.
Otherwise, you can, of course, also implement global, one-shot migrations in the traditional manner.
Production servers require systemd.
๐ฒ Evergreen Web (404 โ 307)
Kitten has built-in support for the Evergreen Web (404 to 307) technique.
To implement 404 to 307 redirection for your Small Web place in Kitten, simply set domain you want to forward to in your Small Web Settings page which you can reach in your browser at /๐ฑ/settings/.
Letโs say you are deploying your new Small Web place at your own domain (e.g., https://ar.al) but you already have a personal web site or a blog there. You will already have content that other people might have linked to and/or rely on. It would be a shame for all those links to break when you deploy your new site.
The Evergreen Web (404 to 307) technique is very simple:
On your new site, issue a 307 redirect for any paths that result in a 404 (not found) error to the previous version of your site. That way, if the path existed in the previous version of your site, it will be found there and, if it did not, it will result in a 404 error there.
Of course, youโre not limited to one level of indirection. If the previous version of your site replaced an even earlier version, thereโs no reason why you cannot add a 404 to 307 rule there also and have a chain of redirects going all the way back to the earliest version of your site.
One of the great advantages of this technique is that you can just leave older versions of your site running (perhaps at different subdomains) and they can all continue to exist using whatever technologies they were using at the time. (You donโt have to take static exports, etc., of the data.)
By using 404 to 307, you will contribute to an evergreen web by not breaking URLs.
To see it in action, run the evergreen-web example.
For more information, see 4042307.org.
Cascading archives
Similar to the Evergreen Web technique, but useful when your old site was static (or if you output a static version of your old site).
Kitten looks for three special folders:
- __archive__1
- __archive__2
- __archive__3
If they exist, it serves their contents as static files from the root of the site.
These routes are added after all other routes.
So if a route cannot be found in the current version of your site, Kitten will search for it in the above archives, in the order shown above.
Valid file types
Kitten doesnโt force you to put different types of routes into predefined folders. Instead, it uses file extensions to know how to handle different routes and other code and assets.
Here is a list of the main file types Kitten handles and how it handles them:
| Extension | Type | Behaviour |
|---|---|---|
| .page.js | Kitten page (JavaScript) | JavaScript route that is compiled into HTML and served in response to a HTTP GET request for the specified path. |
| .page.md | Kitten page (Markdown) | Markdown route with optional front matter that supports JavaScript imports and the use of Kitten components and fragments in Markdown that is compiled into HTML and served in response to a HTTP GET request for the specified path. |
| .post.js, .get.js, .put.js, .head.js, .patch.js, .options.js, .connect.js, .delete.js, .trace.js | HTTP route | Served in response to an HTTP request for the specified method and path. |
| .socket.js | WebSocket route | Served in response to a WebSocket request for the specified path. Only file type to keep part of its file extension in its route pattern (e.g., the file /my.socket.js exists at the URL fragment /my.socket.) |
| .component.js | A component file, returns HTML | Ignored by router. |
| .layout.js | Layout component, returns HTML | Ignored by router. |
| .fragment.js | A fragment file, returns HTML | Ignored by router. |
| .fragment.html | An HTML fragment file | Ignored by router. |
| .fragment.css | A CSS fragment file | Ignored by router. |
| .fragment.md | A Markdown fragment file | Ignored by router. |
| .script.js | Server-side script file | Ignored by router. Useful for including server-side JavaScript modules. File is not accessible from the client. |
| .styles.js | Server-side styles file | Ignored by router. Useful for including server-side CSS (in JS). File is not accessible from the client. |
| Other (.html, .css, .js, .jpg, .gif, etc.) | Static files | Any other files in your project apart from the ones listed above are served as static files. |
HTTP routes
HTTP data routes are served in response to an HTTP request for the specified method and path.
All HTTP request methods are supported.
You create an HTTP route by create a JavaScript file named with the HTTP request method you want to respond to.
For example, to respond to GET requests at /books, you would create a file named books.get.js in the root of your source folder.
The content of HTTP routes is an ESM module that exports a standard Node route request handler that takes http.IncomingMessage and http.ServerResponse arguments.
For example, your books.get.js route might look like this:
export default ({ request, response }) => {
const books = kitten.db.books.get()
response.end(books)
}
URL normalisation
Kitten automatically normalises URLs that do not have a trailing slash when they should to add one.
It does so using a permanent 308 redirect that preserves the method of the request.
This means, for example, that a request to _https://my.site/hello_ will be forwarded to _https://my.site/hello/_.
This is both to help implement canonical paths for your sites/apps and to avoid the unexpected situation of a relative include failing from your page if a slash is not provided in the path. (e.g., if you have an index.js file and an index.html file in a folder called hello and you include the JavaScript file using <script src='./index.js'></script>, that will succeed if the page is hit as /hello/ but fail if it is hit as /hello.)
Sessions and authentication
Kitten automatically manages sessions and authentication for you.
You can add a lock emoji (๐) to the end of any route to make it an authenticated route.
Authenticated routes are only accessible by the owner of the site after they sign in using their secret.
If an authenticated route is a folder, any child routes also require authentication.
You can also manually check if the owner of the site is signed in by checking if request.session.authenticated is true or not.
Finally, you can manually direct people to sign in and out by redirecting them to the following routes:
/๐/sign-in/๐/sign-out
Remember that the Small Web โ and therefore, Kitten โ does not have the concept of users. There is only ever one owner of the site, who is a person. And only this owner can sign into the site. The site might be viewed by countless people over time but they cannot authenticate with it or create accounts on it.
(If you want to build this functionality yourself, for your app, you of course can but it is neither encouraged nor supported. Please see the FAQ for more details.)
The idea is that everyone has their own Small Web site and, when they want to communicate with each other, they do so in a peer-to-peer fashion.
This is the core difference between the Big Web โ which is made of up of centralised silos often owned by trillion-dollar and multi-billion-dollar corporations that make their money by farming you for your data โ and the Small Web, which is a peer-to-peer web of people who use technology as an everyday tool to communicate with each other and enrich their lives.
To understand the technical architecture of the Small Web better, please see the End-to-end encrypted peer-to-peer Small Web apps tutorial.
To learn more about sessions and authentication, please see the following two tutorials:
Data scopes
In Kitten, you have a number of different scopes, with varying lifetimes, where you can store data.
These are, from most limited and ephemeral to widest and most permanent:
| Scope | Lifetime | Availability | Usage | Example |
|---|---|---|---|---|
| Page storage | Life of a single page loaded in a browser window/tab | Only with Streaming HTML workflow (if your page exports an onConnect() handler) | Store data on the data object you have on the page property you get in your page route and page onConnect() handlers.` | Kitten Kawaii |
| Session storage | Common to all pages within a client-side browser session (e.g., multiple pages open in tabs in the same browser. | On any handler that receives a request property | Store data on the session object you have on the request property youโre passed in your page route and page onConnect() handlers. | Sessions tutorial |
| Route globals | Shares state between all handler calls of a route but is ephemeral (destroyed on server restart) | Any route | Create a file-level variable. | Draw Together |
| Globals | Shares state within your application but is ephemeral | Anywhere | Create a global variable (e.g., globalThis.myVariable) | n/a |
| Persisted | Kept in the app database (persisted to disk; survives server restarts) | Anywhere | See Database section, below. | Persistence tutorial |
Database
Kitten has an integrated JSDB database thatโs available from all your routes as db.
JSDB is a transparent, in-memory, streaming write-on-update JavaScript database for the Small Web that persists to a JavaScript transaction log.
Tables in JSDB are simply JavaScript objects or arrays and JSDB writes to plain old JavaScript files.
You can also create type-safe databases. To learn how to do so, read the Database App Modules tutorial.
You can inspect and alter your database from the Kittenโs interactive shell while your app/site is running.
You can also get information about the database and specific tables, delete the whole database and specific tables, and tail specific tables using the command-line interface via the db info [tableName] (or just db [tableName], which is a convenience alias), db delete [tableName] and db tail <tableName> commands.
To access the JavaScript source code of your database, follow the โOpen app data folderโ link in Kittenโs initial terminal output.
Your databases are located under the main data folder:
~/share/small-tech.org/kitten/data/
Each project gets its own folder in there with a name based on the absolute path to your project on your disk. e.g., A Kitten project is stored in /var/home/aral/projects/my-project will have its database in a folder called var.home.aral.projects.my-project in the main data folder.
Route parameters
You can include route parameters in your route paths by separating them with underscores and surrounding the parameter names in square brackets.
For example:
manage_[token]_[domain].socket.js
Will create a WebSocket endpoint at:
/manage/:token/:domain.socket
You can also intersperse path fragments with parameters:
books_[id]_pages_[page].page.js
Will compile the Kitten page and make it available for HTTP GET requests at:
/books/:id/pages/:page
So you can access the route via, say, _https://my.site/books/3/pages/10_.
You can also specify the same routes using folder structures. For example, the following directory structure will result in the same route as above:
my-site
โฐ books
โฐ [id]
โฐ pages
โฐ [page].page.js
Note that you could also have set the name of the page to index[page].page.js_. Using just [page].page.js for a parameterised index page is a shorthand.
You can decide which strategy to follow based on the structure of your app. If, for example, you could access not just the pages but the references and images of a book, it might make sense to use a folder structure:
my-site
โฐ books
โฐ [id]
โ pages
โ โฐ [page].page.js
โ references
โ โฐ [reference].page.js
โฐ images
โฐ [image].page.js
You may, or may not find that easier to manage than:
my-site
โ books_[id]_pages_[page].page.js
โ books_[id]_references_[reference].page.js
โฐ books_[id]_images_[image].page.js
Kitten leaves the decision up to you.
Optional parameters
To create an optional parameter, prefix your parameter name with optional- inside the square brackets.
For example, the following route:
delete
โฐ index_[subdomain]_[optional-return-page].page.js
Will match both of the following paths:
- /delete/my-lovely-subdomain/
- /delete/my-other-subdomain/troubleshooting/
The wildcard parameter
Finally, you can also use the special wildcard parameter to match any string:
my-site
โฐ books_[any].page.js
The about route will match any path that begins with /books/.
Under the hood, Kitten uses Polka for its routing and translates Kittenโs file system-based naming syntax to Polkaโs routing syntax.
App Modules
When working on larger projects, you might end up with pages at varying levels within the site, all of which need to use the same global resource (for example, a layout template or a utility class).
In these situations, your import statements might start to get unruly.
Take the following example:
my-site
โ site.layout.js
โฐ books
โฐ the-handmaids-tail
โฐ pages
โฐ cover.page.js
Letโs say your cover.page.js uses the site layout template.
Hereโs what its import statement would look like:
import Site from '../../../site.layout.js'
export default () => kitten.html`
<${Site}>
โฆ
</>
`
Thatโs both cumbersome to write and error-prone. (Especially when you consider that different pages at different levels of the hierarchy may want to use the same file. You donโt want to spend your day counting dots.)
Enter App Modules.
App Modules are special local Node modules that exist only in your app (not on npm). They live in the special app_modules folder in your project and Kitten knows to ignore them when calculating its routes from the file structure of your project.
App Modules, like all Node modules, need a package.json file but they can get away with specifying a subset of the information contained in ones for npm packages. And, like all Node modules, they need to be npm installed into your project (but from a local file path instead of from npmjs.org).
Finally, if you have type checking enabled in your projects, you should add an index.d.ts file to your App Modules so your editor doesnโt complain when using the TypeScript Language Server (LSP).
Hereโs how youโd make your site layout into an App Module:
Create the app_modules directory and then a directory for your module:
mkdir -p app_modules/site-layoutAdd the files your module will need:
my-site โ app_modules โ โฐ site-layout โ โ site.layout.js โ โ index.d.ts โ โฐ package.json โฐ books โฐ the-handmaids-tail โฐ pages โฐ cover.page.jsIn your package.json file, you need to specify three properties:
name,type, andmain:{ "name": "@app/site-layout", "type": "module", "main": "site.layout.js" }Finally, if you have type checking enabled and you want to avoid type errors in your editor, your index.d.ts file should look like this:
import SiteLayout from './site.layout.js' export default SiteLayout
And thatโs it.
All you need to do then is to install the App Module.
From the root of your project, install your module using its local path:
npm install ./app_modules/site-layout
Finally, from cover.page.js (and any other page, anywhere in your project) you can now import your layout using:
import Site from '@app/site-layout'
Thatโs much better, isnโt it?
Database app modules
Database app modules are special type of app module that Kitten expects to have the name database.
You are not limited to using the default, untyped database Kitten sets up for you.
The default database, like many of the beautiful defaults in Kitten, exists to make it easy to get started with Kitten and use it to build quick Small Web places, teaching programming, etc.
If, however, youโre designing a Small Web place that you will need to maintain for a long period of time and/or that you are going to collaborate with others on, it would make sense to create your own database and to make it type safe so you get both errors and code completion during development time.
You can create your own custom database using a special type of app module called the database app module.
Like any other app module, your database app module goes in the special app_modules directory in the root of your project. Whatโs special is that the app module must be called database.
Make sure your database app modules are using JSDB version 6.0.1 or later as previous versions suffer from a possible data corruption/security issue where string keys were not being sanitised in the same way values were during serialisation.
To learn how to create your own database app module for your Small Web project, please read the Database App Module tutorial.
For a real world example, see the database app module in Domain that wraps Kittenโs global JSDB instance with type information and some utility methods.
Static files
You do not have to do anything special to server static files with Kitten. It is, first and foremost, a web server, after all. Any file thatโs in your project folder that isnโt a hidden file or in a hidden folder and isnโt a special Kitten file (e.g., .page.js, .socket.js, .post.js, etc.), will be served as a static file.
Reserved routes
Kitten tries to be as minimally invasive into your site or appโs own namespace as possible. That said, there are certain routes that it does reserve. Some of these are part of the Small Web protocol and others are Kitten-specific.
Reserved Kitten-specific routes
Kitten-specific routes are kept in the ๐ฑ namespace so as not to pollute your app/siteโs own URI space.
These includes routes for default functionality that Kitten provides like the sign-in and sign-out routes as well as the settings interface:
/๐ฑ/settings (authenticated route)
/๐ฑ/sign-in
/๐ฑ/sign-out
Reserved Small Web protocol routes
The protocol is currently under development and evolving rapidly.
The following routes are reserved as part of the Small Web protocol, which is namespaced by the Small Web emoji/logo (๐).
They are used to implement public-key authentication and manage the communication between Small Web instances and the Domain hosts that host them:
/๐/id
/๐/id/ssh
The Small Web protocol also defines a separate ๐ค (robot) namespace for the server itself (as opposed to the Small Web namespace which is for the person who owns and controls the Small Web place).
Currently, that only contains the following route which returns the serverโs public ED25519 signing key:
- /๐ค/id
(This is used during server-to-server communication to ensure that requests actually originate from the server they purport to.)
Kitten currently does not implement the full Small Web protocol. (The Small Web protocol is, itself, a work in progress.) It is currently up to apps to implement aspects of it in ways that make sense for them.
For example, Place is in the process of implementing the following routes:
- /๐/follow
- /๐/profile
In time weโll see whether or not it makes sense to pull in more base Small Web protocol implementations into Kitten proper.
Kitten client-side libraries
Kitten comes with several client-side libraries which are automatically served at runtime that you can use in your sites/apps.
The page tag provides a high-level interface for including most of them on your pages.
For example, to include htmx, you can add the following page tag to any HTML fragment that ends up on your page:
<page htmx>
You can also include any of them in your sites/apps manually, just like any other client-side script, by knowing their paths:
In /๐ฑ/library/:
| Library name | File name | Page tag attribute name | Version | Notes |
|---|---|---|---|---|
| Kitten cryptography module | crypto-1.js | n/a (ES module) | ^1 | |
| htmx | htmx-2.js | htmx | ^2 | Own fork. |
| htmx WebSocket | htmx-ws-2.js | ws | ^2 | |
| htmx idiomorph extension | htmx-ideomorph-0.3.js | idiomorph | ^0.3.0 | |
| Alpine.js | alpinejs-3.js | alpine | ^3 | |
| Prism (CSS) | prism-1.css | n/a | ^1 | Used internally to syntax highlight Kitten error messages. Also see built-in Highlight.js syntax highlighter. |
| Water (CSS) | water-2.css | water | ^2 |
The libraries also have minified and gzipped version (.min.gz) that are automatically used in production.
The -N notation specifies the major version of the library. Since Kitten sites automatically update, if there is a major version update to a library, it will be adding alongside previous versions so as not to break existing sites. So if htmx 2.x.x comes out, for example, with breaking changes and we start supporting it, it will be added as htmx-2.js. All minor and patch updates will be automatically updated without changing the name of the include.
๐ข Kittenโs interactive shell (REPL)
Introduction
Kitten has an interactive shell (REPL) that extends the default Node.js REPL with Kitten-specific features.
When working with Kitten interactively (i.e., during development), you can launch the shell using the s key from any Kitten instance running in your terminal.
Youโll know youโre in Kittenโs interactive shell when you see the following prompt:
๐ฑ ๐ฌ
If youโve deployed Kitten as a system service (daemon) using systemd, you can use the kitten shell command to connect to the daemon over a socket connection.
Commands
In addition to executing JavaScript statements, Kitten also provides a number of useful custom commands you can use. These commands always begin with a dot.
For a full list of available commands, including ones inherited from Node.jsโs own REPL, use the .help command.
.ls
The .ls command shows you the keys of Kittenโs global kitten object โ one of the most useful objects you can access in Kittenโs shell.
It is a convenience function for the more verbose Object.keys(kitten) JavaScript statement that you can also use for the same purpose.
.id
Shows your Small Web place ID (ed25519 public key) if you have one.
Otherwise, prompts you to use the .id create command to create your Small Web place secret.
.id create
Creates your Small Web place secret (ed25519 private key, encoded as a lovely emoji string that you should keep in your password manager).
The output looks something like the following:
๐ Identity ยป New ID created. ๐ This is your secret:
.settings
Shows the URL for accessing the Kitten Settings page of your app.
e.g., If youโre running it in development on the default port, itโll return:
https://localhost/๐ฑ/settings/
.sign-in
Shows the Sign In page URL for authenticating with your Kitten app.
e.g., If youโre running it in development on the default port, itโll return:
https://localhost/๐/sign-in/
.sign-out
Shows the Sign Out page URL for signing out of your current session on your Kitten app.
e.g., If youโre running it in development on the default port, itโll return:
https://localhost/๐/sign-out/
.stats
Shows statistics about your Kitten app (including statistics on event handlers and listeners) as well as any other Kitten processes that might be active on your machine, as well as basic statistics on all running processes and on the machine itself.
Explore ways you can use Kittenโs shell (REPL) in the Kittenโs Interactive Shell (REPL) tutorial.
Command-line interface (CLI)
Use kitten help to access command information from your terminal.
To get detailed help on a specific command, use kitten help <command name>. For example, kitten help serve.
You can also pass the --help or -h option to any command to get more detailed information about it. So kitten serve --help is equivalent to kitten help serve.
If you have the kitty terminal installed, be aware that it includes a tool also named kitten. This can cause conflicts with the kitten CLI tool, as both share the same name. As a result, they may interfere with each other, and you may find it difficult to launch the correct tool.
In such a situation, you can always start Kitten using the full path to Kitten โ ~/.local/bin/kitten โ or create an alias/symbolic link to it with a different name.
Default command
serve
kitten [path to serve] [options]
kitten serve [path to serve] [options]
Serves a Kitten place.
๐ก If do not specify a path to serve, the default directory (./) is assumed.
Options
--domain: Main domain name to serve site at. Defaults to hostname in production, localhost during development.--aliases: Additional domains to respond to (each gets a TLS cert and 302 redirects to main domain). e.g.,www.<hostname>--port: The port to create the server on. Defaults to 443. Pass --port or --port=0 for random port.--open: Open the page in the default browser.--working-directory: The working directory Kitten was launched from. Defaults to current directory (default .)
When running multiple Kitten instances to test peer-to-peer web functionality during development, remember to use a different domain and port for each instance (for example, place1.localhost for one and place2.localhost:444 for the other) to ensure that sessions work correctly.
This is because Kitten sessions make sure of cookies and cookies do not provide isolation by port.
Kitten supports four localhost subdomains (place1 to place4) that can be run on any valid port for testing peer-to-peer Small Web apps locally.
An apps running on a random port gets a random data directory assigned to it. This directory is only valid for as long as the server is up. The next time you run the same app from a random port, it will get a different data directory and the data from the first run will not carry over. In other words, do not run your app on a random port if it uses a database and relies on data persistence between runs.
The random port feature is useful for local web apps that run locally on your device (the equivalent of command-line applications or native apps).
e.g., see the Markdown Preview utility in the examples folder.
Small Web apps are not meant to be run from random ports on production machines. Always deploy to run from port 443 in live environments (e.g., a VPS or a dedicated device like a single-board computer connected to your home router).
Database commands
db (alias for db info)
kitten db [table name]
kitten db info [table name]
Shows database and table information.
db delete
kitten db delete [table name]
Deletes either the whole database or the table with the specified table name after asking you for confirmation.
db tail
kitten db tail <table name>
Shows a summary of the head (start) and tail (end) of your database table (remember that database tables in JSDB are append-only JavaScript logs) and starts to follow additions to it.
Press Ctrl C to exit as you would a regular shell tail command.
Deployment of Kitten applications
deploy
kitten deploy <git HTTPS clone URL> [options]
Creates a Kitten place by deploying a Kitten project from a git repository as a systemd service.
Options
--domain: Main domain name to serve site at. Defaults to hostname in production, localhost during development.--aliases: Additional domains to respond to (each gets a TLS cert and 302 redirects to main domain). e.g.,www.<hostname>
run
kitten run <git HTTPS clone URL> [options]
Is the little brother of the deploy command. Runs a Kitten project from a git repository locally as a regular process.
Itโs options are the same as that of the default serve command.
Kitten daemon (systemd service) commands
status
kitten status
Shows Kitten systemd service status.
logs
kitten logs [options]
Tails (follows) the Kitten systemd service logs using journald.
Options
--since: Time span to show logs for. For example,6 hours ago,yesterday, etc. (defaults to1 hour ago).
start
kitten start
Starts the Kitten systemd service.
stop
kitten stop
Stops the Kitten systemd service.
enable
kitten enable
Enables the Kitten systemd service (so it auto starts on boot and if the process exits)
disable
kitten disable
Disables the Kitten systemd service (so it no longer auto starts on boot, etc.)
General commands
version (aliases: --version and -v)
kitten version
kitten --version
kitten -v
Displays the Kitten version, made up of:
- Date of the build (the Kittenโs birthday)
- The git commit hash of the build (as represented by the RGB colour equivalent of the hex value; the Kittenโs favourite colour)
- The API version (the major semver component from package.json.)
Eventually, you will be able to tell Kitten what API version your app is written for and it will only auto-update to the latest release in that API version.
help (aliases: --help and -h)
kitten help
kitten help <command name>
kitten <command name> --help
kitten <command name> -h
Use kitten help to access command information from your terminal.
To get detailed help on a specific command, use kitten help <command name>. For example, kitten help serve.
You can also pass the --help or -h option to any command to get more detailed information about it. So kitten serve --help is equivalent to kitten help serve.
update
kitten update
kitten update <API version>
Use kitten update to update (upgrade or downgrade) your currently installed version of Kitten.
When you run the command without supplying the optional API version argument, Kitten will attempt to update to the more recent publicly-available Kitten package from kittens.small-web.org. If your version of Kitten is more recent than the most recent publicly-available Kitten package, Kitten will prompt you to ask if you want to downgrade your installation.
If you supply the optional API version argument, the update will check for the latest version for the given API version. If such an API version does not exist, you will get an error. If your version of Kitten is already on that API version but is a more recent build, you will be prompted whether you want to downgrade your installation.
The only time the downgrade prompt should show is if youโre running a local development build that is more recent that the most recent publicly-available Kitten package. This is useful if you want to quickly test a publicly-available package during development. You can, of course, always install the latest development version by running the ./install script as usual.
uninstall
kitten uninstall
Uninstalls Kittens and removes all Kitten data after asking for confirmation.
Troubleshooting
Here are some edge cases you might encounter (because others have encountered them) and what you can do about it:
Linux: Untrusted localhost TLS certificates in Chrom(ium)
If at some point Chrom(ium) starts complaining that your development-time localhost certificates are untrusted under Linux, check that you havenโt accidentally installed a copy of ca-certificates and/or p11-kit in your unprivileged account using a third-party package manager like Homebrew (brew).
If this is the issue, you should:
- Remove the
ca-certificatesandp11-kitpackages from Homebrew (you will also have to remove any packages that depend on them. e.g., python, OpenSSL, etc.) - Restart your machine.
- Run
sudo update-ca-trust extract - Uninstall Kitten (
kitten uninstall) - Reinstall Kitten via the one-line installation command or from your working copy of the source code (
./install)
That should do it.
To check if your system trust store is set up correctly, you can run the following command:
trust list --filter=ca-anchors | grep Localhost
And you should see output that resembles the following:
label: Localhost Certificate Authority for aral at dev.ar.al
Building Kitten
While developing Kitten, itโs best practice to run the install script and use the kitten command to run your installed build.
./install
A typical run of the install commands takes about half a second on a modern computer so it should not impact your development velocity negatively.
In order to keep the development build/install process as quick as possible, dependencies are not updated unless you specifically request an npm install by passing the --npm flag:
./install --npm
(However, an npm install will be carried out if this is the first time youโre building/installing Kitten locally.)
There is a separate build command (called internally by the install script) and if you use that, you will find the distribution under the dist/ folder.
To run Kitten from the distribution folder, use the following syntax:
dist/kitten [path to serve]
Kittenโs build + install process (what happens when you run the install script) takes only about two seconds on a modern computer and has the additional benefit of informing you of compile-time errors.
Itโs highly recommended you donโt run build by itself or run from the dist folder directly unless you have specific reason to. During everyday development, just run the install script.
Debugging
To run Kitten with the Node debugger active (equivalent of launching Node.js using node --inspect), start Kitten using:
INSPECT=true kitten
If you use VSCodium, you can add breakpoints in your code and attach to the process using the Attach command in the Run and Debug panel.
In Chromium, you can use the Node debugger (enter chrome://inspect in address bar โ select โOpen dedicated DevTools for Nodeโ).
Profiling
You can see profiling information provided by Kittenโs console mixin methods profileTime() and profileTimeEnd() by passing the PROFILE=true environment variable:
e.g.,
PROFILE=true kitten examples/streamiverse
Flame Graphs
To narrow down performance issues by time spent on the stack, you can have Kitten generate a flame graph by setting the FLAME_GRAPH environment variable to true.
e.g.,
FLAME_GRAPH=true kitten examples/streamiverse
This will start your project in production mode and globally install and use the 0x node module to capture profiling information and automatically launch a browser when the process exits to show you the flame graph.
Flame graphs can only be generated in production mode as the 0x module is not compatible with Nodeโs cluster module and the latter is what we use to implement Kittenโs process manager in development mode.
Testing
Kittenโs tests are a work-in-progress and steadily improving.
There are three types of tests in Kitten:
Unit tests
Unit tests are written in Tape With Promises, run using ESM Tape Runner, and displayed using Tap Monkey.
Coverage is provided by c8.
Run tests:
npm -s run unit-tests
Run coverage:
npm run -s coverage
The -s flag just silences the npm logs for cleaner output.
Unlike regression tests and end-to-end tests (see below), unit tests are run on the Kitten source code instead of the built Kitten bundle.
Regression tests
Regression tests are written with the same stack as Kittenโs unit tests but use the Kitten and Browser test helpers to test initial output from test fixtures (the example apps).
These do not test client-side interactions, for that, please see end-to-end tests, below.
(In the future, these tests might be removed in favour of only keeping the end-to-end tests as they test similar things. The only advantage of the regression tests is that they are faster to run as they test using JSDOM instead of actual browsers.)
Run tests:
npm run -s regression-tests
Regression tests initially perform a Kitten install to ensure you are testing with the latest Kitten bundle. If you do not want this, run the regression-tests-without-install tast instead of the regression-tests task.
End-to-end tests
End-to-end tests use Playwright, along with our custom Kitten helper for controlling the Kitten server to test Kittenโs behaviour using actual browser engines (Safari/Webkit, Firefox/Gecko, Chromium/Blink).
Run tests:
npm run -s end-to-end-tests
Like regression tests, end-to-end tests perform Kitten install prior to running the tests. To skip this, run the end-to-end-tests-without-install task.
By default, tests run in headless browsers. You can also run the tests in Chromium with the interface showing so you can see the state of the web interface at every state of the tests. To run the tests this way, execute the end-to-end-tests-in-browser task. There is also a end-to-end-test-in-browser-without-install task you can use to skip the install Kitten install.
All tests
To perform a Kitten install and run all tests:
npm -s test
This will run unit, regression, and end-to-end tests.
Deployment (of Kitten itself)
To build and deploy a new Kitten package to kittens.small-web.org,run the deploy script:
./deploy
You will need to add the secret deployment token to your system in order to deploy. If you have deployment rights for Kitten, follow the instructions available at https://kittens.small-web.org/settings.
Core dependencies
| Dependency | Purpose |
|---|---|
| @small-tech/https | Drop-in replacement for Nodeโs native https module with automatic TLS for development and production using @small-tech/auto-encrypt and @small-tech/auto-encrypt-localhost. |
| @small-tech/jsdb | Zero-dependency, transparent, in-memory, streaming write-on-update JavaScript database that persists to JavaScript transaction logs. |
| Polka@next | Native HTTP server with added support for routing, middleware, and sub-applications. Polka uses Trouter as its router. |
| tinyws | WebSocket middleware for Node.js based on ws. |
| xhtm | XHTM is alternative implementation of HTM without HTM-specific limitations. Low-level machinery is rejected in favor of readability and better HTML support. |
| hyperscript-to-html-string | Render xhtm/Hyperscript to HTML strings, without virtual DOM. |
| sanitize-html | Clean up untrusted HTML, preserving whitelisted elements and whitelisted attributes on a per-element basis. Built on htmlparser2 for speed and tolerance |
| node-git-server | Git server for hosting your source code. Used in deployments. |
| isomorphic-git | Git client used in deployments on development and for handling auto-updates on production. |
| sade | A small command-line interface (CLI) framework that uses mri for its argument parsing. |
| noble-ed25519 | Cryptography: ed25519 key generation. |
| ed25519-keygen | Cryptography: SSH key generation from ed25519 key material. |
Kittenโs renderer is built upon xhtm and vhtml although both of those libraries have been inlined and extended.
For an automatically-generated full list of modules and contributors, please see CONTRIBUTORS.md.
Cryptographic properties
(The functionality described in this section is currently being developed both in Domain and Kitten.)
Overview
The security (and privacy) of Domain/Kitten are based on a 32-byte cryptographically random secret string that only the person who owns/controls a domain knows.
This is basically a Base256-encoded ed25519 secret key where the Base256 alphabet is a set of curated emoji surrogate pairs without any special modifiers chosen mainly from the animals, plants, and food groups with some exceptions (to avoid common phobias or triggers, etc.) that we call KittenMoji.
๐ต๐๐ฆ๐ฆง๐ถ๐๐ฆฎ๐ฉ๐บ๐ฆ๐ฆ๐ฑ๐๐ฆ๐ฏ๐
๐๐ด๐งฎ๐ฆ๐ฆ๐ฆ๐ฆฌ๐ฎ๐๐๐๐ท๐๐๐ฝ๐
๐๐๐ช๐ซ๐ฆ๐ฆ๐๐ฆฃ๐ฆ๐ฆ๐ญ๐๐๐น๐ฐ๐
๐๐ฆซ๐ฆ๐ฆ๐ป๐จ๐ผ๐ฆฅ๐ฆฆ๐ฆจ๐ฆ๐ฆก๐พ๐ฆ๐น๐
๐ฃ๐ค๐ฅ๐ฆ๐ง๐๐ฆ
๐ฆ๐ฆข๐ฆ๐ฆค๐ชถ๐ฆฉ๐ฆ๐ฆ๐ฒ
๐๐ข๐ฆ๐๐๐ฆ๐ฆ๐ณ๐๐ฌ๐ฆญ๐๐ ๐ก๐ฆ๐
๐๐๐ฆ๐๐๐๐ชฒ๐๐ฆ๐ญ๐๐งฌ๐ชฑ๐ฆ ๐๐ธ
๐ ๐๐น๐งฃ๐บ๐ป๐ผ๐ท๐ฑ๐ชด๐ฒ๐ณ๐ด๐ต๐พ๐ฟ
๐ค๐๐๐ชบ๐ฝ๐๐๐๐๐๐๐๐ฅญ๐๐๐
๐๐๐๐ซ๐ฅ๐
๐ซ๐ฅฅ๐ฅ๐๐ฅ๐ฅ๐ฝ๐งธ๐ซ๐ฅ
๐ฅฌ๐ฅฆ๐ง๐ง
๐๐ฅ๐ฐ๐๐ฅ๐ฅ๐ฉ๐ฅจ๐ฅฏ๐ฅ๐ง๐ง
๐ถ๐ธ๐พ๐จ๐๐ญ๐๐ญ๐ฅช๐ฎ๐ฏ๐ธ๐ท๐๐ฅ๐
๐ผ๐๐พ๐ป๐ฅ๐ฟ๐งฉ๐๐ฅซ๐ธ๐๐๐๐๐๐
๐ ๐ข๐ฃ๐ค๐ฅ๐ฅฎ๐ก๐ฅ๐ฅ ๐ฉฐ๐ฆ๐ฆ๐ฆ๐ฆ๐ก๐ฆ
๐ง๐จ๐ฉ๐ช๐๐ฐ๐ง๐ฅง๐ซ๐ฌ๐ญ๐ฎ๐๐ผ๐ฎ๐น
๐ซ๐๐๐๐งญ๐ ๐ช๐ช๐งต๐งถ๐ง๐๐ช๐๐๐
The KittenMoji standard was frozen in time on Friday, November 25th, 2022.
The string created by joining the list of the full KittenMoji alphabet has the following hash:d9b1533de36a75c9e47f1713aada4ff332918ff0d3cada5b645ca84d73dd50d6.
When setting up a Small Web app via Domain, this key is generated in the personโs browser, on their own computer, and is never communicated to either the Domain instance or the Kitten app being installed. Instead the ed25519 public key is sent to both and signed token authentication is used when the server needs to verify the ownerโs identity (e.g., before allowing access to the administration area).
The expected/encouraged behaviour is for the person to store this secret in their password manager of choice.
From this key material, we derive SSH keys and the personโs server is set up to allow SSH access via this key.
Kitten running on a development machine can also recreate these SSH keys and configure the personโs SSH access to their server when the secret key is provided.
The personโs ed25519 public key is used as their identity within the system and enables their Small Web place (Kitten app) to communicate with the Domain instance for administrative reasons (e.g., to cancel hosting, etc.)
Threat model
The audience for Domain/Kitten and the Small Web in general is everyday people who use technology as an everyday thing and want privacy by default in the systems they use.
If you are an activist, etc., who might be specifically targetted by nation states, etc., please do not use this system as it does not protect against, for example, infiltration of hosting providers by nation state actors.
Given that the secret key material is generated in a web app, you must trust that the code being served by the server is what you expect it to be. Currently, there is no validation mechanism for this, although this is on the roadmap going forward (via, for example, a browser extension). This is not a problem unique to web apps, given that native apps are commonly dynamically built by app stores (at which point a nation state actor could inject malicious code) and given the lack of verifiable builds in mainstream supply chains.
You can, however, overcome this limitation by hosting Domain yourself, on your own hardware (e.g., a single-board computer like a Raspberry Pi).
The other thing to be aware of is that the security of the system is based on your secret key remaining secret and, initially at least, there will not be a way to change this secret key. (On the longer roadmap, it would be nice to provide a means of changing it but this is not a trivial process, especially if encryption keys have been derived from it and encrypted messages exist within a Kitten app).
Finally, as of September 2024 when this was last updated, the major browser engines do not yet all support ed25519/x25519 in the Web Crypto API. Specifically, while Apple (Safari) and Mozilla (Firefox) have implemented it, Google (Chrome/Chromium) hasnโt. This means that we must use a JavaScript library to handle the cryptography ad store the secret key in local storage on the client. Therefore, if a Kitten app is compromised via a Cross-Site Scripting (XSS) attack, the secret can be obtained. Once Chrome and Chromium-based browsers implement the feature by default (it is currently behind a flag), Kitten will migrate to using the Web Crypto API and the secret key will be stored in an inextractable manner that is safe from exposure via XSS.
Questions?
Contact Aral on the fediverse.