npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@proteles/htmx

v0.1.1

Published

htmx server helpers for Proteles authentication: HX-Redirect-aware login flows, route guards, and fragment guards. Server-only — htmx needs no client-side auth script.

Downloads

260

Readme

@proteles/htmx

htmx server helpers for Proteles authentication.

Server-only, on purpose. htmx keeps no client state — the server renders the HTML — so there is no browser bundle to load and no client-side auth script to keep in sync. Tokens live in an encrypted, httpOnly cookie and never reach the browser at all.

The problem this solves

htmx follows redirects with XHR. So when an unauthenticated hx-get hits a protected route and your server answers 302 → /api/auth/login:

  1. the browser's XHR quietly follows the redirect,
  2. htmx receives the login page's HTML,
  3. and swaps it into whatever hx-target said.

You get a login form nested inside a table cell, and the address bar never changes. It's the most common htmx auth bug.

Every helper here answers with htmx's own escape hatch instead — 200 plus an HX-Redirect header — but only when the caller was htmx. A normal page load still gets a proper 3xx, so the same route works both ways.

Install

npm install @proteles/htmx

Setup

import http from "node:http";
import { getUser, guardRequest, protelesAuthHandler, requireUser, sendNodeResponse } from "@proteles/htmx";

http.createServer(async (req, res) => {
  const event = { node: { req } };
  const url = new URL(req.url, "http://localhost:3000");

  // 1. The four auth routes.
  if (url.pathname.startsWith("/api/auth/")) {
    return sendNodeResponse(res, await protelesAuthHandler(event));
  }

  // 2. A protected page.
  if (url.pathname === "/dashboard") {
    const redirect = await guardRequest(event, { publicPaths: ["/"] });
    if (redirect) return sendNodeResponse(res, redirect);
    const user = await getUser(event);
    return html(res, `<h1>Hi ${user.email}</h1>`);
  }

  // 3. A fragment that needs a user.
  if (url.pathname === "/fragments/secret") {
    const auth = await requireUser(event);
    if (auth.response) return sendNodeResponse(res, auth.response);
    return html(res, `<p>🔒 for ${auth.user.email}</p>`);
  }
}).listen(3000);

Your HTML needs no auth-specific attributes:

<button hx-get="/api/auth/login">Sign in</button>
<button hx-post="/api/auth/logout">Sign out</button>

<!-- an auth-aware fragment the server renders either way -->
<div hx-get="/fragments/user" hx-trigger="load" hx-swap="outerHTML"></div>

hx-post="/api/auth/logout" is the case people hit first: a plain 303 there would have htmx swap the post-logout page into a fragment.

API

| Export | Purpose | | --- | --- | | protelesAuthHandler(event, config?) | Handles login/callback/logout/me, htmx-aware | | guardRequest(event, { publicPaths }, config?) | A response to send when the request should bounce to login, else undefined | | requireUser(event, config?) | { user } or { response } — the per-route/fragment guard | | getUser(event, config?) | The sanitized user (no tokens) | | attachUser(event, config?) | Resolves and caches the user on the event's context/locals | | getSession(event, config?) | The full session including tokens — server-side only |

htmx protocol helpers

| Export | Purpose | | --- | --- | | isHtmxRequest(source) | Reads HX-Request; works with a Request, Headers, { headers }, or an h3-style event | | isBoosted(source) | Reads HX-Boosted | | htmxRedirect(url) | 200 + HX-Redirect (a full browser navigation) | | htmxLocation(url) | 200 + HX-Location (a client-side navigation) | | withHtmxTrigger(res, event, detail?) | Adds HX-Trigger, e.g. to refresh a user menu after sign-in | | toHtmxResponse(res, source) | Rewrites a 3xx to HX-Redirect only for htmx callers, preserving Set-Cookie |

toHtmxResponse is what makes the shared BFF safe here, and preserving Set-Cookie matters: the login response carries a redirect and the encrypted flow cookie, and the callback carries the session cookie and a flow-clear.

@proteles/bff's AuthApp, toWebRequest, and sendNodeResponse are re-exported, so this is the only Proteles package an htmx app installs.

Note on the callback

/api/auth/callback is reached by a real browser navigation from the authorization server, so it is not an htmx request and correctly keeps its ordinary redirect. That asymmetry is deliberate and covered by tests.

Configuration (environment)

Read by @proteles/bff: PROTELES_ISSUER, PROTELES_CLIENT_ID, PROTELES_CLIENT_SECRET, PROTELES_SESSION_SECRET (base64 of 32 random bytes), PROTELES_APP_URL or PROTELES_REDIRECT_URI, plus optional PROTELES_SCOPES, PROTELES_COOKIE_SECURE, PROTELES_BASE_PATH.

Local-dev gotcha on Node 18

Point PROTELES_ISSUER at 127.0.0.1, not localhost. Node 18's fetch resolves localhost to IPv6 ::1 first, and an IPv4-bound authorization server refuses that — the browser redirect works but the server-side token exchange fails. The BFF logs the reason server-side if a login bounces back.

Develop

npm install      # from the sdk/ workspace root
npm run build    # tsc -> dist
npm test         # tsx + node:test

26 unit tests, most asserting the htmx and browser paths side by side. Because htmx auth is purely a matter of which headers come back, the behaviour is also fully verified over real HTTP against a live authorization server as Test 14f of scripts/e2e-smoke-test.sh (see verify-htmx.mjs) — and end-to-end in a real browser with real htmx, confirming that a protected fragment request navigates to login instead of injecting the login form into the page.