This is a brief tutorial detailing how I’ve set up new Rails 6 apps with PostgreSQL, Tailwind CSS, and Stimulus.

Update: I wrote this post back in October, and for whatever reason I never published it. I’m publishing it now, backdated to emphasize that I make no guarantees that everything in here is up-to-date. I will say, however, that with the Hotwire announcement and Tailwind 2.0, a setup like this has become even more attractive.

Requirements

This post assumes you have Ruby, Rails, and PostgreSQL installed on a Unix-based operating system (like macOS or Linux). These instructions were created using Ruby 2.7.1, Rails 6.0.3.2, and Postgres 12.3, but will probably work fine on other setups as long as you’re on Rails 6 or greater.

Rails Setup

We’ll be creating a new rails app called myapp. Replace myapp with the desired name of your app throughout this post.

Create a new Rails project, using Postgres as the database:

rails new myapp -d=postgresql
cd myapp

Gitignore

Add a .gitignore file in the project’s root directory. Here’s a good one:

# Ignore bundler config.
/.bundle

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/
!/tmp/pids/.keep

# Ignore uploaded files in development.
/storage/*
!/storage/.keep

/public/assets
.byebug_history

# Ignore master key for decrypting credentials and more.
/config/master.key

/public/packs
/public/packs-test
/node_modules
/yarn-error.log
yarn-debug.log*
.yarn-integrity

I also like to ignore all files generated by my IDE, RubyMine, so I add this line:

# Ignore Jetbrains files
**/.idea/

You might want to include your IDE’s files or include just some of them. Or maybe you don’t use an IDE. Adapt accordingly.

Readme, Changelog, and License

It’s at this point I add a readme, changelog, and license file to the project root. How you do this and in what format is up to you. I use markdown files for all three, and base my changelog on the format detailed at Keep a Changelog.

Rubocop

Code linters are a point of contention in the development community. My preference is to use the heavily-tuned-down Rubocop config used in the official Rails repo. I literally just dump this file as-is into my project root. You may have to set up Rubocop manually depending on your setup. RubyMine handles that for me.

If you decide to use Rubocop, you’ll want to add the gems to your Gemfile. For all Rubocop setups, you’ll need the rubocop gem. If you’re using the Rails project’s config I mentioned above, you’ll need two additional gems.

Here’s what I have in my Gemfile, under the development group:

group :development do
  gem "rubocop", ">= 0.47", require: false
  gem "rubocop-performance", require: false
  gem "rubocop-rails", require: false
  # There's some other stuff in here by default, too.
end

Bundle

Time to install all your gems if you haven’t already. Make sure you’re using rbenv or an equivalent.

bin/bundle install

Database Setup

Before we hook things up to Rails, make sure Postgres is ready to go. You should create a role named myapp with a password and the CREATEDB privilege. Instructions on creating a role like this are widely available on the internet. You can do it through the shell or via a program like DataGrip or pgAdmin. Make sure to note down the password, you’ll need it.

Once the role is created, add its name and password to config/database.yml. For example, if you set the password to beanburrito, your file would contain something like this:

# In config/database.yml
development:
  <<: *default
  database: myapp_development
  
  username: myapp
  password: beanburrito

Now you should be able to run the database setup and initial migration:

bin/rails db:setup db:migrate

Webpacker Setup

At the time of this writing, by default, Rails uses webpack for JavaScript and the asset pipeline for CSS/SCSS/Sass. I know some people like to run both their JavaScript and CSS through webpack. The projects I’ve worked on recently take that route. I pass no judgement here, but if you want to do that, you’ll need to rejigger your files a bit, as follows.

Note: For the purposes of this post, I’m writing these instructions for a pure CSS project. You can use a SCSS or Sass with Tailwind just fine, though. For info on that, check out this section of the Tailwind docs.

To run your CSS through webpack, move the stylesheets directory at app/assets/stylesheets to app/javascript/stylesheets. Some people like to rename the javascript directory at this point since it now contains CSS as well, but I leave that up to you.

You’ll also want to open app/views/layouts/application.html.erb and change stylesheet_link_tag to stylesheet_pack_tag.

Now you can create a file at app/javascript/stylesheets/application.css to be the main entrypoint for your CSS. Then, import it in your webpack entrypoint:

// In app/javascript/packs/application.js
import "../stylesheets/application.css"

Tailwind Setup

Next up is Tailwind installation. I’ve been using Tailwind for a year or two, and I really dig it. The utility-first structure seems pretty good, and it’s easily to customize and extend beyond the built-in utilities.

Anyway, install Tailwind via yarn:

yarn add tailwindcss

After that’s done, you’ll want to create a config file in the project root named tailwind.config.js. Here’s a skeletal config you can use:

// tailwind.config.js
module.exports = {
  purge: [],
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}

Import the core tailwind styles in your application.css file:

@import "tailwindcss/base";
@import "tailwindcss/components";

/* If you add other imports, they should go here. */

@import "tailwindcss/utilities";

Finally, update your PostCSS config in the project root. You’ll need to include tailwindcss and autoprefixer, so your file might end up looking like this:

// In postcss.config.js
module.exports = {
  plugins: [
    require('tailwindcss'),
    require('autoprefixer'),
    require('postcss-import'),
    require('postcss-flexbugs-fixes'),
    require('postcss-preset-env')({
      autoprefixer: {
        flexbox: 'no-2009'
      },
      stage: 3
    })
  ]
}

PurgeCSS

Eventually, you’ll want to set up PurgeCSS for use in production, but I don’t want to spend too much time on that here. Make sure to read that page so you understand what’s happening, and then you’ll want to add a purge section to your Tailwind config that will look something like this:

// In tailwind.config.js
module.exports = {
  purge: {
    content: ["./app/views/**/*.html.erb", "./app/views/**/*.js.erb"],
  },
  // The rest of your config goes here.
}

I highly recommend that you read that page about PurgeCSS, because it has important information on how you need to write your views so the purge will work properly. Without purge, you will end up with oversized asset files.

Stimulus Setup

Let’s get Stimulus working. First, install it with yarn:

yarn add stimulus

Then all you need to do is load Stimulus and your controllers:

// In app/javascript/packs/application.js
import { Application } from "stimulus";
import { definitionsFromContext } from "stimulus/webpack-helpers";
const application = Application.start();
const context = require.context("../controllers", true, /\.js$/);
application.load(definitionsFromContext(context));

That configuration will cause your app to look for Stimulus controllers in app/javascript/controllers/.

Running

At this point, you should be good to go. Start up the webpack dev server from your apps’s root directory so your app will hot reload when any changes are made to your CSS or JS:

./bin/webpack-dev-server

Then you can start up the Rails server and get hacking!

rails s

Feedback

Questions, comments, or tips for me? See a mistake in this post? Send me an email.