DEV Community

Cover image for Adventure: Building with NATS Jetstream KV Store -Part 7
Richard
Richard

Posted on • Edited on

Adventure: Building with NATS Jetstream KV Store -Part 7

Hey there!

Welcome back again to my adventure series on the NATS JetStream Key/Value Store!

Welcome to Part 7 of our series on the NATS JetStream KV Store! We're going to get right into this because right now our project is very very ugly.


Let's make things pretty!

We're going to add Tailwind CSS to our project.

# Install pnpm and run
$  cd $HOME/Projects/ds_nats_ttt

# Install TailwindCSS
$  pnpm init

# Add TailwindCSS and DaisyUI
$  pnpm add -D tailwindcss@3.4.10 postcss@8.4.41 autoprefixer@10.4.20 daisyui@4.12.10
Enter fullscreen mode Exit fullscreen mode

Awesome sauce! Now that TailwindCSS is in place, it's time to fine-tune our setup by tweaking package.json and tailwind.config.js. These changes will ensure our styles are built efficiently and work seamlessly with our project.


Package.json

Here's my version, but feel free to edit and customize it however you like! Whether you prefer a different approach or just want to add your own touch, go for it!

{
  "name": "ds_nats_ttt",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "autoprefixer": "10.4.20",
    "daisyui": "4.12.10",
    "postcss": "8.4.41",
    "tailwindcss": "3.4.10"
  }
}
Enter fullscreen mode Exit fullscreen mode

Tailwind.Config.js

Let's generate our tailwind config.

# Install our dependencies
$  pnpm i

# Generate your tailwind.config.js
$  pnpm exec tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

We're bringing DaisyUI themes back into the mix - because, we're lazyUI! Instead of manually styling everything, we'll let DaisyUI handle the heavy lifting.

Since I'm a fan of that old-school aesthetic, I'm rolling with the retro theme. Want to explore all the available themes? You can check them out here and pick one that matches your style!

/** @type {import('tailwindcss').Config} */
import daisyui from "daisyui";

export default {
  content: [
    "./web/**/*.html",
    "./web/**/*.templ",
    "./web/**/*.go",
    "./web/static/*.js",
  ],
  theme: {
    extend: {},
  },
  plugins: [daisyui],
  daisyui: {
    themes: [
      "retro",
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

To ensure we apply this theme, we need to add a data attribute to our template. This is both fitting and a hint of what's to come because Datastar itself operates using data attributes!

We'll explore this in more detail later, but that's the inspiration behind the name - data-*. Clever, right? I knew you'd catch on!

Add data-theme="retro" to our Index.templ file. We also should add a link tag for our styles.

package layouts

templ Base() {
    <!DOCTYPE html>
    <html lang="en" data-theme="retro">
        <head>
            <title>Tic+Tac+Toe</title>
            <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
            <link rel="icon" type="image/x-icon" href=""/>
            <link href="/static/index.css" rel="stylesheet" type="text/css"/>
        </head>
        <body class="min-h-screen w-full bg-base-content">
            { children... }
        </body>
    </html>
}

Enter fullscreen mode Exit fullscreen mode

In our web/styles/styles.css file, add this…

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Now that TailwindCSS is set up, we could manually generate styles by running commands - but who wants to do that? Remember, we're all about lazyUI! Instead, let's automate the process using Taskfile to handle it for us effortlessly.


Automated build, hot-reload, use some tools to share the load!

Ok that made me sound a bit like Samwise Gamgee but it's ok - because he was the real hero in the end.

Let's create a Taskfile so we can make this entire process easier on ourselves. Go head and install taskfile.

Taskfile

Now all we gotta do is create a Taskfile.yml in our project root.

version: "3"

env:
  STATIC_DIR: "web/static"

tasks:
  # -------------------------------
  # Build Tasks (For Production)
  # -------------------------------

  build:templ:
    desc: "Generate Go templates using templ"
    cmds:
      - go run github.com/a-h/templ/cmd/templ@v0.2.793 generate -v

  build:styles:
    desc: "Compile TailwindCSS styles"
    cmds:
      - pnpm exec tailwindcss -c tailwind.config.js -i web/styles/styles.css -o $STATIC_DIR/index.css

  build:
    desc: "Compile the Go project and generate necessary assets"
    cmds:
      - mkdir -p tmp
      - go build -o tmp/main .
    deps:
      - build:styles
      - build:templ
    parallel: true

  # -------------------------------
  # Debugging Tasks
  # -------------------------------

  debug:
    desc: "Run project with Delve debugger"
    cmds:
      - go run github.com/go-delve/delve/cmd/dlv@v1.23.1 exec ./tmp/main
    deps:
      - build

  # -------------------------------
  # Live Reload (For Development)
  # -------------------------------

  live:templ:
    desc: "Watch for templ changes and regenerate templates"
    cmds:
      - go run github.com/a-h/templ/cmd/templ@v0.2.793 generate --watch --proxy="http://localhost:8080" --open-browser=false -v

  live:styles:
    desc: "Watch and recompile TailwindCSS styles"
    cmds:
      - pnpm exec tailwindcss -c tailwind.config.js -i web/styles/styles.css -o $STATIC_DIR/index.css --watch

  live:server:
    desc: "Auto-reload the Go server with Air"
    cmds:
      - mkdir -p tmp
      - |
        go run github.com/air-verse/air@v1.52.3 \
        --build.cmd "go build -o tmp/main ." \
        --build.bin "tmp/main" \
        --build.delay "100" \
        --build.exclude_dir "node_modules,data,public" \
        --build.include_ext "go" \
        --build.stop_on_error "false" \
        --misc.clean_on_exit true

  live:reload:
    desc: "Watch for changes in static files and trigger a reload"
    cmds:
      - |
        go run github.com/air-verse/air@v1.52.3 \
        --build.cmd "go run github.com/a-h/templ/cmd/templ@v0.2.793 generate --notify-proxy" \
        --build.bin "true" \
        --build.delay "100" \
        --build.exclude_dir "" \
        --build.include_dir "$STATIC_DIR" \
        --build.include_ext "css"

  live:
    desc: "Run the full live-reloading development environment"
    deps:
      - live:templ
      - live:styles
      - live:reload
      - live:server

  # -------------------------------
  # Running the Application
  # -------------------------------

  run:
    desc: "Run the compiled Go binary"
    cmds:
      - ./tmp/main
    deps:
      - build

  default:
    desc: "Default task: runs the application"
    deps:
      - run
Enter fullscreen mode Exit fullscreen mode

We can break this file down--essentially tasks "build" on each other. We start by setting the static directory env variable. Then we have a list of tasks.

env:
  STATIC_DIR: "web/static"

tasks:
  # -------------------------------
  # Build Tasks (For Production)
  # -------------------------------
Enter fullscreen mode Exit fullscreen mode

Tasks can be composed of other tasks. As you can see below we have a build:templ task. It will essentially run templ generate . like we did earlier.

build:templ

build:templ:
    desc: "Generate Go templates using templ"
    cmds:
      - go run github.com/a-h/templ/cmd/templ@v0.2.793 generate -v
Enter fullscreen mode Exit fullscreen mode

We also have a command to build our styles. Pretty straight forward; it runs pnpm exec with tailwindcss to compile off our config based on our base styles. Then we output this to our static directory to serve in our templates.

build:styles

build:styles:
    desc: "Compile TailwindCSS styles"
    cmds:
      - pnpm exec tailwindcss -c tailwind.config.js -i web/styles/styles.css -o $STATIC_DIR/index.css
Enter fullscreen mode Exit fullscreen mode

build

Next we have our actual build task itself. You can see this depends on our styles and templ builds. So one can deduce that we build those first …in parallel? YES! In parallel! Then we build our project main.go.

build:
    desc: "Compile the Go project and generate necessary assets"
    cmds:
      - mkdir -p tmp
      - go build -o tmp/main .
    deps:
      - build:styles
      - build:templ
    parallel: true
Enter fullscreen mode Exit fullscreen mode

debug

We use the delve library to efficiently debug our application, making it easier to inspect and troubleshoot code with precision.

# -------------------------------
  # Debugging Tasks
  # -------------------------------

  debug:
    desc: "Run project with Delve debugger"
    cmds:
      - go run github.com/go-delve/delve/cmd/dlv@v1.23.1 exec ./tmp/main
    deps:
      - build
Enter fullscreen mode Exit fullscreen mode

We also harness the power of Air. We're an airbender for all intents and purposes. This allows us seamless live reloading, ensuring our Go server instantly reflects code changes without manual restarts.

live:templ

This works its magic to regenerate templates, keeps a hawk-eye on changes, slyly proxies requests through localhost:8080, politely refuses to pop open your browser, and runs with all the verbose flair of a chatty parrot.

# -------------------------------
  # Live Reload (For Development)
  # -------------------------------

  live:templ:
    desc: "Watch for templ changes and regenerate templates"
    cmds:
      - go run github.com/a-h/templ/cmd/templ@v0.2.793 generate --watch --proxy="http://localhost:8080" --open-browser=false -v
Enter fullscreen mode Exit fullscreen mode

live:styles

PNPM takes the reins to run TailwindCSS, keeps a watchful eye on our styles, dumps the chic compiled styles our static directory, and recompiles whenever it spots a change.

live:styles:
    desc: "Watch and recompile TailwindCSS styles"
    cmds:
      - pnpm exec tailwindcss -c tailwind.config.js -i web/styles/styles.css -o $STATIC_DIR/index.css --watch
Enter fullscreen mode Exit fullscreen mode

live:server

Air swoops in to reload our Go server, ensures /tmp exists (because it's polite), builds our app and tucks the binary into /tmp/main. Then it watches our Go files, ignores node_modules, data, and public like they're drama, waits a cool 100ms before restarting, powers through errors like a champ, and cleans up after itself like the tidy hero it is.

live:server:
    desc: "Auto-reload the Go server with Air"
    cmds:
      - mkdir -p tmp
      - |
        go run github.com/air-verse/air@v1.52.3 \
        --build.cmd "go build -o tmp/main ." \
        --build.bin "tmp/main" \
        --build.delay "100" \
        --build.exclude_dir "node_modules,data,public" \
        --build.include_ext "go" \
        --build.stop_on_error "false" \
        --misc.clean_on_exit true
Enter fullscreen mode Exit fullscreen mode

live:reload

Air keeps its eyes peeled for CSS changes like a fashion critic, fires up Templ generate the moment it spots an update, stalks our static directory for CSS file drama, and waits a stylish 100ms before hitting that reload button - because good things come to those who (briefly) wait.

live:reload:
    desc: "Watch for changes in static files and trigger a reload"
    cmds:
      - |
        go run github.com/air-verse/air@v1.52.3 \
        --build.cmd "go run github.com/a-h/templ/cmd/templ@v0.2.793 generate --notify-proxy" \
        --build.bin "true" \
        --build.delay "100" \
        --build.exclude_dir "" \
        --build.include_dir "$STATIC_DIR" \
        --build.include_ext "css"
Enter fullscreen mode Exit fullscreen mode

live

I think this one is self-explanatory but we do all the crazy stuff we just did above because this one depends on all of them!

live:
    desc: "Run the full live-reloading development environment"
    deps:
      - live:templ
      - live:styles
      - live:reload
      - live:server
Enter fullscreen mode Exit fullscreen mode

Run

You can see this one just builds our project.

  # -------------------------------
  # Running the Application
  # -------------------------------

  run:
    desc: "Run the compiled Go binary"
    cmds:
      - ./tmp/main
    deps:
      - build

  default:
    desc: "Default task: runs the application"
    deps:
      - run
Enter fullscreen mode Exit fullscreen mode

When all is said and done you can run these commands to do the various things stated above. Go ahead and try it now.

# Let's generate our new styles and then run it
$  task build:styles
$  task live
Enter fullscreen mode Exit fullscreen mode

BOOM! Everything should build and be watching and all the great things stated above. You can go to https://localhost:8080 to confirm and aso check out the hot-reloading proxy at https://localhost:7331.


Holy-moly that was a lot! However we now having a working project that's ready to go! Now we actually get to the fun fun fun part. Making things happen. Part 7!

Top comments (0)