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:
- 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 < 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
- 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!