The "No BS" Guide to Rails 8 + HTMX + SQLite3

This guide is for developers who want to build something quickly without wading through paragraphs of theory or watching lengthy tutorial videos. What We're Building A simple app that: Uses Rails 8 and TailwindCSS 4 Has HTMX for interactivity (Turbo and Stimulus are awesome though) SQLite3 for storage, but also Solid Cable, Solid Cache, and Solid Queue Lets you add posts without page refreshes (like it's 2025 or something) Has no Node / NPM dependency whatsoever, at any step Setting up the project First, let's create a new Rails app. I'm assuming you have RVM / a recent Ruby version installed and properly set up. rails new my_rails_app --skip-javascript cd my_rails_app/ Update your Gemfile Open your Gemfile and add these gems: # Add to the main group gem "tailwindcss-rails" gem "importmap-rails" # Add to the development and test groups group :development, :test do # ... your existing development gems gem "foreman" end These gems serve specific purposes: Tailwind: Provides utility classes to avoid writing custom CSS Importmap: Enables JS module usage without complex build steps Foreman: Manages multiple processes in a single terminal Install All The Things Time to install gems and set things up: bundle install rails tailwindcss:install rails importmap:install bin/importmap pin htmx.org@2.0.4 rails generate controller Home index rails generate model Post title:string rails db:migrate db:seed rails generate controller Posts This does a bunch of stuff in one go: Installs our gems Sets up Tailwind and importmap Adds HTMX to our app Creates a Home controller with an index action Makes a Post model with a title field Creates and migrates the database Generates a Posts controller (empty for now) Make HTMX Play Nice with Rails HTMX needs to send CSRF tokens with requests or Rails will reject them. Add this to app/javascript/application.js: import "htmx.org" document.addEventListener('DOMContentLoaded', () => { const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); document.body.addEventListener('htmx:configRequest', (event) => { event.detail.headers['X-CSRF-Token'] = token; }); }); Want to save bandwidth? Use a CDN instead to serve the HTMX lib. In config/importmap.rb, replace its pin with: pin "htmx.org", to: "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js" Configure Routes Keeping it minimal. Edit config/routes.rb: root "home#index" resources :posts, only: [:create] We only need two routes: Homepage (root) Create posts endpoint Seed Some Data Let's add some fake posts. In db/seeds.rb: Post.create!(title: "First Post") Post.create!(title: "Hello, SQLite!") Post.create!(title: "HTMX + Rails 8 + SQLite Demo") Then run: rails db:seed Home Controller: Keep It Simple Update app/controllers/home_controller.rb: class HomeController This does several things: Shows a heading Creates a form that sends data to /posts with HTMX Tells HTMX to add the response to the end of #post-list Lists all existing posts Uses Tailwind classes to validate the CSS builds as expected via foreman The Post Partial Create app/views/posts/_post.html.erb: Just a list item with the post title. Start Your App Run this to start everything (it'll leverage Foreman behind the scenes): bin/dev Now visit http://localhost:3000 and try adding a post! Bonus: Rubymine Setup Run/Debug Configuration Click the vertical 3 dots near the "Debug/Run" buttons Select "Configuration > Edit..." Click "+", select "Gem Command" Set it up: Name: "Foreman" (or whatever) Gem name: foreman Executable name: foreman Arguments: start -f Procfile.dev Database Setup Double-click storage/development.sqlite3 in the project tree This opens the "Data Sources and Drivers" dialog Default options should work fine - install driver if needed Find your data under development > main > tables > posts And done! You now have a clean and fresh codebase to start your venture into Rails 8 with no Node/NPM/Yarn/Webpacker dependency - neither during developement, build steps, nor at runtime in production. Enjoy!

Apr 1, 2025 - 00:30
 0
The "No BS" Guide to Rails 8 + HTMX + SQLite3

This guide is for developers who want to build something quickly without wading through paragraphs of theory or watching lengthy tutorial videos.

What We're Building

A simple app that:

  • Uses Rails 8 and TailwindCSS 4
  • Has HTMX for interactivity (Turbo and Stimulus are awesome though)
  • SQLite3 for storage, but also Solid Cable, Solid Cache, and Solid Queue
  • Lets you add posts without page refreshes (like it's 2025 or something)
  • Has no Node / NPM dependency whatsoever, at any step

Setting up the project

First, let's create a new Rails app. I'm assuming you have RVM / a recent Ruby version installed and properly set up.

rails new my_rails_app --skip-javascript
cd my_rails_app/

Update your Gemfile

Open your Gemfile and add these gems:

# Add to the main group
gem "tailwindcss-rails"
gem "importmap-rails"

# Add to the development and test groups
group :development, :test do
  # ... your existing development gems
  gem "foreman"
end

These gems serve specific purposes:

  • Tailwind: Provides utility classes to avoid writing custom CSS
  • Importmap: Enables JS module usage without complex build steps
  • Foreman: Manages multiple processes in a single terminal

Install All The Things

Time to install gems and set things up:

bundle install
rails tailwindcss:install
rails importmap:install
bin/importmap pin htmx.org@2.0.4
rails generate controller Home index
rails generate model Post title:string
rails db:migrate db:seed
rails generate controller Posts

This does a bunch of stuff in one go:

  • Installs our gems
  • Sets up Tailwind and importmap
  • Adds HTMX to our app
  • Creates a Home controller with an index action
  • Makes a Post model with a title field
  • Creates and migrates the database
  • Generates a Posts controller (empty for now)

Make HTMX Play Nice with Rails

HTMX needs to send CSRF tokens with requests or Rails will reject them. Add this to app/javascript/application.js:

import "htmx.org"
document.addEventListener('DOMContentLoaded', () => {
    const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
    document.body.addEventListener('htmx:configRequest', (event) => {
        event.detail.headers['X-CSRF-Token'] = token;
    });
});

Want to save bandwidth? Use a CDN instead to serve the HTMX lib. In config/importmap.rb, replace its pin with:

pin "htmx.org", to: "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"

Configure Routes

Keeping it minimal. Edit config/routes.rb:

root "home#index"
resources :posts, only: [:create]

We only need two routes:

  1. Homepage (root)
  2. Create posts endpoint

Seed Some Data

Let's add some fake posts. In db/seeds.rb:

Post.create!(title: "First Post")
Post.create!(title: "Hello, SQLite!")
Post.create!(title: "HTMX + Rails 8 + SQLite Demo")

Then run:

rails db:seed

Home Controller: Keep It Simple

Update app/controllers/home_controller.rb:

class HomeController < ApplicationController
  def index
    @posts = Post.all
  end
end

Simple and direct - just retrieve all posts.

Posts Controller: Even Simpler

Update app/controllers/posts_controller.rb:

class PostsController < ApplicationController
  def create
    @post = Post.create!(title: params[:title])
    render partial: "posts/post", locals: { post: @post }
  end
end

This creates a post and returns just the HTML for the new post. HTMX will insert it into the page.

The Homepage View

Replace app/views/home/index.html.erb with:

 class="text-center">
   class="text-4xl font-bold text-blue-500 mb-4">SQLite3 + HTMX Demo

   hx-post="/posts" hx-target="#post-list" hx-swap="beforeend" class="mb-4">
     type="text" name="title" placeholder="New Post" class="p-2 border rounded">
     class="px-4 py-2 bg-blue-500 text-white rounded">Add Post
  

   id="post-list" class="mt-4 text-lg text-gray-700">
    <% @posts.each do |post| %>
      <%= render partial: "posts/post", locals: { post: post } %>
    <% end %>
  

This does several things:

  • Shows a heading
  • Creates a form that sends data to /posts with HTMX
  • Tells HTMX to add the response to the end of #post-list
  • Lists all existing posts
  • Uses Tailwind classes to validate the CSS builds as expected via foreman

The Post Partial

Create app/views/posts/_post.html.erb:

 class="p-2 border-b border-gray-300"><%= post.title %>

Just a list item with the post title.

Start Your App

Run this to start everything (it'll leverage Foreman behind the scenes):

bin/dev

Now visit http://localhost:3000 and try adding a post!

Bonus: Rubymine Setup

Run/Debug Configuration

  1. Click the vertical 3 dots near the "Debug/Run" buttons
  2. Select "Configuration > Edit..."
  3. Click "+", select "Gem Command"
  4. Set it up:
    • Name: "Foreman" (or whatever)
    • Gem name: foreman
    • Executable name: foreman
    • Arguments: start -f Procfile.dev

Database Setup

  1. Double-click storage/development.sqlite3 in the project tree
  2. This opens the "Data Sources and Drivers" dialog
  3. Default options should work fine - install driver if needed
  4. Find your data under development > main > tables > posts

And done!

You now have a clean and fresh codebase to start your venture into Rails 8 with no Node/NPM/Yarn/Webpacker dependency - neither during developement, build steps, nor at runtime in production. Enjoy!