Index āŗ 10. Fetching and working with data
This tutorial is the first of the two Fetchiverse tutorials where you work with data from the fediverse (specifically, from Aralās fediverse instance).
Topics covered
- Kittenās Node.js runtime.
- Node modules.
- Nodeās Fetch API.
- The fediverse.
- Working with APIs.
- Kittenās default HTML sanitisation and using the explicit [code]safelyAddHtml[code] function.
- How to specify boolean HTML attributes in Kitten templates.
Fetchiverse
Since Kitten uses Node.js as its runtime, you can install, import, and use Node modules in your project just like in any other Node.js project.
š” Kitten installs the latest long-term support (LTS) version of Node.js as its runtime.
You donāt need to install Node or npm yourself to make sites and apps with Kitten.
If you need to use npm, Kitten provides a handy alias to its version of npm with the
kitten-npm
command. Similarly, you can access Kittenās version of node using thekitten-node
command.Note that Kitten is now using Node 20 even though itās not LTS yet to avoid a performance regression introduced in Node 18 that affects OCSP stapling.
That said, Kitten also has commonly-used global APIs you can use without installing or importing them.
Youāve already seen one of those, the JavaScript Database (JSDB), which is available via the global db
reference.
Similarly, the Fetch API is available for use as fetch
.
Hereās an example of how to use the Fetch API to get the list of public posts from a Mastodon instance.
Welcome to the fediverse
This is the instance weāll be using: https://mastodon.ar.al
And this is the JSON endpoint with the public timeline data: https://mastodon.ar.al/api/v1/timelines/public
Take a look at both to understand what weāre working with before creating a new folder called fetchiverse with a file called index.page.js in it.
Now add the following code to that file:
export default async function route () {
const postsResponse = await fetch('https://mastodon.ar.al/api/v1/timelines/public')
const posts = await postsResponse.json()
return kitten.html`
<h1>Aralās Public Fediverse Timeline</h1>
<ul>
${posts.map(post => (
kitten.html`
<li>
<a class='avatar-link' href='${post.account.url}'>
<img class='avatar' src='${post.account.avatar}' alt='${post.account.username}ās avatar'>
</a>
<div class='content'>
${kitten.safelyAddHtml(post.content)}
${post.media_attachments.map(media => (
media.type === 'image' ? kitten.html`<img class='image' src='${media.url}' alt='${media.description}'>` : ''
))}
</div>
</li>
`
))}
</ul>
<style>
body { font-family: sans-serif; font-size: 1.25em; padding-left: 1.5em; padding-right: 1.5em; }
h1 { font-size: 2.5em; text-align: center; }
p:first-of-type { margin-top: 0; }
p { line-height: 1.5; }
a:not(.avatar-link) {
text-decoration: none; background-color: rgb(139, 218, 255);
border-radius: 0.25em; padding: 0.25em; color: black;
}
ul { padding: 0; }
li {
display: flex; align-items: flex-start; column-gap: 1em; padding: 1em;
margin-bottom: 1em; background-color: #ccc; border-radius: 1em;
}
.avatar { width: 8em; border-radius: 1em; }
.content { flex: 1; }
.image { max-width: 100%; }
</style>
`
}
Run Kitten and hit https://localhost to see the latest public timeline from Aralās mastodon instance.
š Notice the call to
kitten.safelyAddHtml()
when rendering the postās content.This is content that we donāt necessarily trust so we have to be careful with how we add it to our page.
(OK, in this case, we know weāre using HTTPS for the connection to Aralās Mastodon server and we can be reasonably sure that Aral, his host (toot.io *waves at Jan*), and Mastodonās source code are trustworthy but still⦠who knows, maybe Aral got hacked and his instance is sending carefully-crafted
<script>
tags in the content to exfiltrate your secrets.)To see what happens if we forget to add this global function call, remove it and run the example again. Ah, you see the escaped content. So Kitten tries to be as safe by possible by default and will escape any strings you attempt to interpolate into your page. However, in this case, we do want to display HTML. We just want to display a subset of safe HTML.
For content like this, Kitten provides the convenient global
kitten.safelyAddHtml()
method that uses the sanitize-html module.
š” In this example you saw how to set attributes on HTML tags for the first time.
Here, weāre only setting string values and they work as you would expect. However, there are also HTML boolean attributes that work differently in HTML than, well, pretty much anywhere else:
If a boolean HTML attribute is present, it is ātrueā. If it is absent, it is āfalse.ā
An example of such a boolean HTML attribute is
disabled
.To make matters even more confusing, you cannot set an HTML boolean attribute to
false
. If you do, e.g., if you setdisabled=false
, your element will end up disabled.To specify an HTML boolean attribute in Kitten, you can use the following idiom:
let disabled = true kitten.html` <button ${disabled && 'disabled'}>Press me!</button> `
The above code outputs
<button disabled>ā¦</button>
.
š¾ This example is available in the examples/fetchiverse folder of the Kitten source code.
Our Fetchiverse example is working great but could we refactor it ā in other words, improve the code quality without changing its functionality ā to make it even better?
Yes we can! And in the process, we will also learn how to use components and fragments in Kitten.
Next tutorial: Components and fragments