Adventure: Building with NATS Jetstream KV Store -Part 8
Welcome welcome! Welcome back again to my adventure series on the NATS JetStream Key/Value Store! Welcome to Part 8 of our series on the NATS JetStream KV Store! We're finally hitting some real development. We're going to cover how Datastar works a bit and use it to hit our endpoints and display some UI! Where did we leave off? We had just created multiple boring pages and styled them in our application. Now let's add more! Routes routes routes First, let's create our other routes. We will have 3 pages, an Index page at the root, a Dashboard page for game lobbies and then a page for unique Games. Let's create them! $ touch /internal/routes/dashboard.go /internal/routes/game.go $ touch /ui/pages/dashboard.templ /ui/pages/game/templ After generating the necessary files, the next step is to define the routes and corresponding pages. Routes Dashboard.go package routes import ( "net/http" "github.com/go-chi/chi/v5" "github.com/rphumulock/ds_nats_ttt/ui/pages" ) func setupDashboardRoute(router chi.Router) error { router.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) { pages.Dashboard().Render(r.Context(), w) }) return nil } This follows the same routing approach we implemented earlier. Game.go package routes import ( "net/http" "github.com/go-chi/chi/v5" "github.com/rphumulock/ds_nats_ttt/ui/pages" ) func setupGameRoute(router chi.Router) error { router.Get("/game/{id}", func(w http.ResponseWriter, r *http.Request) { pages.Game().Render(r.Context(), w) }) return nil } Similar to the previous route, but with the addition of a parameter in the endpoint URL. This will hold our unique game ids. Router.go package routes import ( "context" "errors" "fmt" "log/slog" "github.com/go-chi/chi/v5" ) func SetupRoutes(ctx context.Context, logger *slog.Logger, router chi.Router) (cleanup func() error, err error) { cleanup = func() error { return errors.Join() } if err := errors.Join( setupIndexRoute(router), ); err != nil { return cleanup, fmt.Errorf("error setting up routes: %w", err) } return cleanup, nil } Nice! Nothing too wild here—pretty straightforward again. Once we run task live, Templ will do the heavy lifting and generate our templates for us. Easy peasy! Check it out We can also see our new pages. Dashboard Game Alright, great. We've got buttons that don’t do anything and pages we have to navigate to manually... yeah, this is not it. Let's fix it. Don't worry, Enter Datastar Datastar Ok so what's Datastar about? On the front of the Datastar site it says... "Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework." Oh neat, another front-end framework. Haha, well sort of but not really. Datastar was born from the same ideas that created HTMX. I'm sure you've heard of it. The approach is a bit different than the common SPA's people use such as React. A Brief Overview Traditionally, SPAs rely on JavaScript frameworks like React to fetch JSON from an API and dynamically build the UI. While techniques like Server-Side Rendering (SSR) and WebSockets exist, most applications use a standard request-response cycle, where JSON is retrieved and then rendered into the UI. HTMX HTMX, on the other hand, changes this paradigm. Instead of fetching JSON and constructing the UI in JavaScript, it directly requests HTML fragments from the server. These fragments are then dynamically inserted into the DOM without a full page reload, providing a smoother, more efficient experience. This approach resembles traditional multi-page applications (MPAs), but with the advantage of partial page updates rather than full reloads. Despite this shift, HTMX still operates in a request-response pattern—you're just receiving ready-to-render HTML instead of raw JSON. This allows you to build rich, interactive applications with minimal JavaScript, keeping logic on the server where it belongs. For a deeper dive into this, check out HyperMedia Systems, an excellent read where one of the authors happens to be the creator of HTMX. Datastar Datastar takes this concept a step further. Like HTMX, it delivers HTML fragments that are rendered directly into the DOM, but instead of a pull-based approach, it operates more like a push from the server. Rather than fetching HTML on demand, Datastar leverages Server-Sent Events (SSE) to stream HTML fragments from the server to the front-end. While you still initiate a request, the response isn’t limited to a single payload—instead, the server can continuously send one-to-many HTML fragments over time. SSE establishes a however-long-you-want-lived HTTP connection, unlike traditional request-re

Welcome welcome!
Welcome back again to my adventure series on the NATS JetStream Key/Value Store!
Welcome to Part 8 of our series on the NATS JetStream KV Store! We're finally hitting some real development. We're going to cover how Datastar works a bit and use it to hit our endpoints and display some UI!
Where did we leave off?
We had just created multiple boring pages and styled them in our application. Now let's add more!
Routes routes routes
First, let's create our other routes. We will have 3 pages, an Index page at the root, a Dashboard page for game lobbies and then a page for unique Games.
Let's create them!
$ touch /internal/routes/dashboard.go /internal/routes/game.go
$ touch /ui/pages/dashboard.templ /ui/pages/game/templ
After generating the necessary files, the next step is to define the routes and corresponding pages.
Routes
Dashboard.go
package routes
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/rphumulock/ds_nats_ttt/ui/pages"
)
func setupDashboardRoute(router chi.Router) error {
router.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) {
pages.Dashboard().Render(r.Context(), w)
})
return nil
}
This follows the same routing approach we implemented earlier.
Game.go
package routes
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/rphumulock/ds_nats_ttt/ui/pages"
)
func setupGameRoute(router chi.Router) error {
router.Get("/game/{id}", func(w http.ResponseWriter, r *http.Request) {
pages.Game().Render(r.Context(), w)
})
return nil
}
Similar to the previous route, but with the addition of a parameter in the endpoint URL. This will hold our unique game ids.
Router.go
package routes
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/go-chi/chi/v5"
)
func SetupRoutes(ctx context.Context, logger *slog.Logger, router chi.Router) (cleanup func() error, err error) {
cleanup = func() error {
return errors.Join()
}
if err := errors.Join(
setupIndexRoute(router),
); err != nil {
return cleanup, fmt.Errorf("error setting up routes: %w", err)
}
return cleanup, nil
}
Nice! Nothing too wild here—pretty straightforward again. Once we run task live
, Templ will do the heavy lifting and generate our templates for us. Easy peasy!
We can also see our new pages.
Alright, great. We've got buttons that don’t do anything and pages we have to navigate to manually... yeah, this is not it. Let's fix it.
Don't worry, Enter Datastar
Datastar
Ok so what's Datastar about? On the front of the Datastar site it says...
"Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework."
Oh neat, another front-end framework. Haha, well sort of but not really. Datastar was born from the same ideas that created HTMX. I'm sure you've heard of it. The approach is a bit different than the common SPA's people use such as React.
A Brief Overview
Traditionally, SPAs rely on JavaScript frameworks like React to fetch JSON from an API and dynamically build the UI. While techniques like Server-Side Rendering (SSR) and WebSockets exist, most applications use a standard request-response cycle, where JSON is retrieved and then rendered into the UI.
HTMX
HTMX, on the other hand, changes this paradigm. Instead of fetching JSON and constructing the UI in JavaScript, it directly requests HTML fragments from the server. These fragments are then dynamically inserted into the DOM without a full page reload, providing a smoother, more efficient experience.
This approach resembles traditional multi-page applications (MPAs), but with the advantage of partial page updates rather than full reloads.
Despite this shift, HTMX still operates in a request-response pattern—you're just receiving ready-to-render HTML instead of raw JSON. This allows you to build rich, interactive applications with minimal JavaScript, keeping logic on the server where it belongs.
For a deeper dive into this, check out HyperMedia Systems, an excellent read where one of the authors happens to be the creator of HTMX.
Datastar
Datastar takes this concept a step further. Like HTMX, it delivers HTML fragments that are rendered directly into the DOM, but instead of a pull-based approach, it operates more like a push from the server.
Rather than fetching HTML on demand, Datastar leverages Server-Sent Events (SSE) to stream HTML fragments from the server to the front-end. While you still initiate a request, the response isn’t limited to a single payload—instead, the server can continuously send one-to-many HTML fragments over time.
SSE establishes a however-long-you-want-lived HTTP connection, unlike traditional request-response cycles where the connection closes immediately after the response is received. With SSE, the server decides when to close the connection, allowing for real-time updates without requiring multiple requests from the client.
SSE is quite literally just a different Content-Type Header on the server.
We've got a high-level overview of Datastar—but don’t worry, there will be a dedicated series on that later. Right now, we're here for NATS, dang it!
So, let’s use Datastar to dive back into some NATS goodness!