Kitten

Explore Kitten’s built-in support for multi-part forms and file uploads as you build a simple image upload example.

Topics covered

Kitten has high-level support for multi-part form handling and file uploads.

Uploads sent to POST routes via <input type='file'> in your pages are automatically saved in your project’s uploads folder. Kitten automatically assigns them unique IDs and serves them from the /uploads/<unique-id> route. The Upload objects are also available to your POST routes in the request.uploads array.

💡 An upload object has the following properties:

.id           // Unique id. Used to look up uploads and calculate resource paths.
.fileName     // Name of the original file that was uploaded.
.filePath     // Absolute path to uploaded file on server.

.resourcePath // Relative URL resource path the upload can be downloaded from. 

.mimetype     // MIME type of file.
.field        // Name of file upload field in form that file was uploaded from.
.encoding     // Encoding of file.
.truncated    // Whether file was truncated or not (boolean).
.done         // Whether upload was successfully completed or not (boolean).

And the following method:

.delete()     // Deletes the upload.

A common idiom is to save the upload’s unique ID (e.g., request.uploads[0].id), along with any other data in your form (e.g., the alt-text of an image upload), in your own database tables. Then, when you want to, say, render an uploaded image on a page, you can use the global kitten.upoads object to reference the upload you need and access its resource path.

e.g.,

kitten.html`
  <img src='${kitten.uploads.get(uploadId).resourcePath}' alt='…'>
`

💡 The kitten.uploads collection has the following methods:

.get(id)    // Returns Upload object with given ID (or undefined, if it doesn’t exist).
.all()      // Returns array of all Upload objects.
.allIds()   // Returns array of strings of all Upload object IDs.
.delete(id) // Deletes object with given id (or fails silently if it doesn’t exist).

Kitten can handle multiple file uploads as well as single ones.

💡Note that you must set the enctype='multipart/form-data' attribute on your forms for file uploads to work correctly.

The following basic example shows just how easy it is to handle file uploads in Kitten. In it, you can upload one image at a time along with its alt-text and displays them in a grid at the top of the page:

index.post.js

export default function ({ request, response }) {
 request.upoads.forEach(upload => {
   kitten.db.images.push({
     path: upload.resourcePath,
     altText: request.body.altText ? request.body.altText : upload.fileName
   })
 })
 response.get('/')
}

index.page.js

if (!kitten.db.images) kitten.db.images = []

export default () => kitten.html`
<h2>Uploaded images</h2>

<if ${kitten.db.images.length === 0}><p>None yet.</p></if>

<ul>
  ${kitten.db.images.map(image => kitten.html`
    <img src=${image.path} alt=${image.altText}>
  `)}
</ul>

<h2>Upload an image</h2>

<form method='post' enctype='multipart/form-data'>
  <label for='image'>Image</label>
  <input type='file' name='image' accept='image/*'>
  <label for='alt-text'>Alt text</label>
  <input type='text' id='alt-text' name='altText'>
  <button type='submit'>Upload</button>
</form>

<style>
  body { max-width: 640px; margin: 0 auto; padding: 1em; font-family: sans-serif; }
  ul { padding: 0; display: grid; grid-template-columns: 1fr 1fr; }
  img { max-height: 30vh; margin: 1em; }
  input { width: 100%; margin: 1em 0; }
  button { padding: 0.25em 1em; display: block; margin: 0 auto; }
</style>
`

You can find the code for the above example in the examples/file-uploads folder of the Kitten source code. There is also an Ajax version of the example in examples/file-uploads-ajax and a Streaming HTML version in examples/streaming-html/file-uploads

Now that you know how to work with multi-part forms and file uploads, let’s return to the Fetchiverse example and extend it to create a streaming interface of curated public fediverse posts using Kitten’s first-class support for Hypermedia-driven application development using htmx, the htmx WebSocket extension, and Kitten’s socket routes (.socket.js files).

Next tutorial: htmx, the htmx WebSocket extension, and socket routes