DEV Community

Kuba
Kuba

Posted on

2

Instant Message Display in Chat Apps with htmx

Problem overview

While building my AI assistant, I needed a simple chat interface to test it locally. Building a whole React app seemed overkill, so I tried HTMX. The challenge was that the response logic took time (at least 5 calls to OpenAI API) for a single query. For better UX, I wanted the user message displayed instantly and the assistant’s response appended afterward.

Solution

TLDR: repo is here: https://github.com/JakubSzwajka/hono-htmx-chat

The idea is pretty simple:

  • Send query and expect only the assistant’s response in ready-to-display HTML.
  • Append user message to the conversation upon form submission.

For this example I’ve added setTimeout to simulate some logic.

// index.ts (so called 'backend') 

const app = new Hono()

const messages: { user: string, message: string }[] = []

app.get('/', serveStatic({ path: './static/index.html' }))

app.get('/messages', (c) => {
  return c.html(messages.map(msg => `<p><strong>${msg.user}:</strong> ${msg.message}</p>`).join(''))
})

app.post('/message', async (c) => {
  const formData = await c.req.formData()
  const message = formData.get('message') as string
  messages.push({ user: 'user', message })

  await new Promise(resolve => setTimeout(resolve, 2000))

  // Simple response logic
  const responseMessage = `Received your message: ${message}`
  messages.push({ user: 'Assistant', message: responseMessage })

  return c.html(`
    <p><strong>Assistant:</strong> ${responseMessage}</p>
  `)
})
Enter fullscreen mode Exit fullscreen mode

Client logic here does the trick. Simple function that appends user query to the conversation body.

// index.html 
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat App</title>
    <script src="https://unpkg.com/htmx.org@1.6.1"></script>
  </head>
  <body>
    <h1>Chat App</h1>
    <div id="chat" hx-get="/messages" hx-trigger="load" hx-swap="innerHTML">
      <!-- Chat messages will be displayed here -->
    </div>
    <form id="messageForm" hx-post="/message" hx-trigger="submit"
      hx-swap="beforeend"
      hx-target="#chat"
      onsubmit="handleSubmit(event)">
      <input type="text" id="message" name="message" placeholder="Your message"
        required>
      <button type="submit">Send</button>
    </form>
  </body>
  <script>
    function handleSubmit(event) {
      event.preventDefault();
      const message = new FormData(event.target).get('message');
      const newMessage = document.createElement('p');
      newMessage.innerHTML = `<strong>User:</strong> ${message}`;
      document.getElementById('chat').appendChild(newMessage);
    }

  </script>
</html>
Enter fullscreen mode Exit fullscreen mode

💣 HTML structure for a single message is repeated in a few places. You can extract it into some JSX with Hono, but I didn't cover that here.

Want to Know More?

Stay tuned for more insights and tutorials! Visit My Blog 🤖

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay