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 [email protected]
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/[email protected]/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!