Skip to content

deriegle/express-hotwire

Repository files navigation

Express library for working with Hotwire (HTML over the wire)

This project was built to provide tooling for working with the new @hotwired/turbo package built by Basecamp for their NEW MAGIC used with hey.com. I wanted to try building similar tooling in express as an example for how this can implemented in other languages, but also to help me learn how it works under the hood.

Using the library

  1. Add the express-hotwire package to your project

Using npm

npm install express-hotwire

Using yarn

yarn add express-hotwire
  1. Use the middleware with your app
const express = require('express');
const expressHotwire = require('express-hotwire');

const app = express();

app.use(expressHotwire());
  1. Render your turbo stream responses using the res.turboStream object.
app.post('/messages', async (req, res) => {
  const { content } = req.fields;

  // create message and save in database/memory/etc
  const message = create(content);

  // Make sure the first argument matches the HTML element id that you want to append a child to
  await res.turboStream.append('messages', {
    partial: 'messages/show', // This should be inside your views directory as views/messages/show.ejs
    locals: {
      // Add any variables needed to render the partial
      message,
    },
  });
});

Turbo Stream Messages and Actions

We provide helpful methods for all the actions listed in the hotwire.dev docs.

// The contents of the partial will be appended to the element with DOM ID "messages".
res.turboStream.append('messages', {
  partial: 'messages/show',
  locals: {
    message: { id: 1, content: 'Hi' },
  },
});

// The contents of the partial will be prepended to the element with the DOM ID "messages".
res.turboStream.prepend('messages', {
  partial: 'messages/show',
  locals: {
    message: { id: 1, content: 'Hi' },
  },
});

// The contents of the partial will replace the existing element with the DOM ID "message_1".
res.turboStream.replace('message_1', {
  partial: 'messages/show',
  locals: {
    message: { id: 1, content: 'Hi' },
  },
});

// The contents of the partial will update the element with DOM ID "unread_count".
res.turboStream.update('unread_count', {
  partial: 'messages/show',
  locals: {
    message: { id: 1, content: 'Hi' },
  },
});

// The element with DOM ID "message_1" will be removed.
res.turboStream.remove('message_1');

Sending Multiple TurboStreams

Sometimes you want to return multiple turbo streams from a request handler. You can use the res.turboStream.multiple function to do that. You have access to all the same turboStream methods in the callback as defined above.

Note: You cannot call turboStream.multiple from within the callback. We don't support nested calls to .multiple

await res.turboStream.multiple((turboStream) => [
  turboStream.append('messages', {
    partial: 'messages/show',
    locals: {
      message: { id: 1, content: 'Hi' },
    },
  }),
  turboStream.prepend('messages', {
    partial: 'messages/show',
    locals: {
      message: { id: 1, content: 'Hi' },
    },
  }),
  turboStream.replace('message_1', {
    partial: 'messages/show',
    locals: {
      message: { id: 1, content: 'Hi' },
    },
  }),
  turboStream.update('unread_count', {
    partial: 'messages/show',
    locals: {
      message: { id: 1, content: 'Hi' },
    },
  }),
  turboStream.remove('message_1'),
]);