Affimon

Sign in Pricing

← Home // Published 27th January, 2025

Our Production Ruby on Rails Stack

Affimon is a link-scanning SaaS built with Ruby on Rails, a full-stack web framework that I'm a big fan of.

I wrote this article to explore some of the infrastructure and gem choices I've made with Affimon, from the basics like background jobs and my blog system (that powers this article!) to the deployment stack and some fun extras.

I hope you find this article interesting, and maybe discover an interesting new gem or two!

The basics (db, jobs, caching, payments etc.)

gem "pg"                    # database
gem "redis"                 # cache
gem "sidekiq"               # background jobs
gem "sidekiq-scheduler"     # scheduled jobs
gem "rspec-rails"           # test framework

The core Affimon stack is pretty vanilla — it uses PostgreSQL for the database, Redis for caching and job queues, and Sidekiq for job processing. I haven't bought into the no PAAS, local first hype that came with Rails 8. Don't get me wrong, it's awesome to have a local-first option, but technologies like PG and Sidekiq are so battle-tested and well-supported in the Rails ecosystem these days that it'll be difficult to convince me to switch.

I use the Sidekiq Scheduler gem for recurring background jobs (ie: each morning at 1am a job runs to determine which websites to scan for the day), and I veer slightly off the "Rails way" with RSpec for tests. This is just a preference thing, I've got no qualms with Minitest!

gem "pay"                   # payments and subscriptions
gem "stripe"                # payments adapter

For Payments, I use the excellent Pay gem from the folks at GoRails, and Stripe as the payments processor.

The Pay gem is a huge timesaver and is deeply integrated with Stripe. It handles webhooks, subscription statuses and more, which takes a huge load off my shoulders (I don't have to roll a custom payments solution). This gem frees me up to focus on other stuff, it costs nothing, and I'm confident that it'll keep getting maintained well into the future.

Authentication (Devise, OAuth)

gem "devise"                            # Auth
gem "activerecord-session_store"        # Session storage
gem "omniauth-rails_csrf_protection"    # Make Oauth and Devise play together nicely
gem "omniauth-google-oauth2"            # Support "login with Google"

Devise is the de-facto default authentication framework for Ruby on Rails apps, so that's what I use.

I've probably re-implemented this OAuth flavor of Devise 30+ times by now for various projects; I'm pretty sure I could do it in my sleep — I use the omniauth-google-oauth2 gem to handle the actual OAuth flow, and couple it with gems for csrf protection and sessions storage to make sure everything plays together nicely.

This article is my go-to reference for implementing this flow, plus theres this in-depth expanded version if you need more info.

Performance and Profiling (Bullet, Rack Mini Profiler)

gem "rack-mini-profiler"    # Page rendering insights, SQL counts etc
gem "bullet"                # Automatically alerts for N+1 queries

I use the Bullet gem to help eliminate N+1 queries and combine it with rack-mini-profiler to keep an eye on performance. It's common for websites in Affimon to have 30k+ links associated, so preloading and efficient queries are important to keep things feeling snappy.

These gems are genuinely helpful too! They helped me eliminate some inefficient preloading and counting across multiple pages, which improved load times by 2-4x.

I only run these gems in the development environment, but I saw recently that Campsite runs rack-mini-profiler in production, which I might look into.

View layer (TailwindCSS and ViewComponents)

gem "tailwindcss-rails" # use TailwindCSS
gem "view_component"    # better version of partials
gem "rails_heroicon"    # easily use icons from https://heroicons.com

The view layer has been getting a bit of love lately in the Rails world, thanks to gems like ViewComponent which I use heavily. I've written about ViewComponents before — they plug in very neatly to the standard Rails stack, feel excellent to work with and are a huge step up from regular partials.

For styling, I'm a huge fan of TailwindCSS. I know TailwindCSS can be divisive, but it helps me style UIs so quickly that I don't think I could ever go back. It also plays nicely with Rails and ViewComponent.

I pair vanilla TailwindCSS with components from TailwindUI and icons from Heroicons (via the rails_heroicon gem) to quickly build and iterate on the UI for Affimon.

Blogging

gem "redcarpet"             # markdown parsing
gem "rouge"                 # code highlighting
gem "sitemap_generator"     # sitemap builder
gem "front_matter_parser"   # parse frontmatter from .md files

The main Ruby on Rails monolith for Affimon also powers this blog, mainly leveraging the Redcarpet gem. Redcarpet powers all the markdown rendering, which I pipe through a TailwindCSS prose class (learn more) to easily render my blog posts.

I pair Redcarpet with Rouge for code highlighting (like above), using the included plugin for Redcarpet and the base16.solarized.light theme (preview other themes).

I also use the Sitemap Generator gem to create an XML sitemap, and frontmatterparser to extract data from each post, like the date of publication.

Hosting and deployment

I use Hatchbox to deploy Affimon and host on servers from Hetzner.

Hetzner is absurdly good value — Affimon currently runs on a single CAX31 instance, which offers 8 vCPU cores, 16 GB of RAM, 160 GB of storage and 20 TB of bandwidth... for US $15.50/mo 🤯.

Hatchbox costs $10/mo on top of the server costs, but I find it to be, again, great value. It eliminates deployment headaches and handles configuring the server, keeping it up to date, deploying new builds from GitHub, handling my domain names, SSL and much more. I use Hatchbox for other projects too and have always been pleased.

I haven't looked much into Kamal despite it being the "hot new thing". It seems to demand more DevOps knowledge than I'm comfortable with — if I were to go back down the bare-metal route though, I'd probably use Dokku, at least while Kamal is still in its infancy.

Extras (Dashboard, Charts, DevX etc)

gem "blazer"        # dashboards & queries
gem "chartkick"     # charts (bar, line, pie etc.)
gem "annotate"      # annotate models, specs etc
gem "letter_opener" # preview emails in development

Above are a couple of handy extra gems I use in Affimon. The Blazer gem is an awesome gem that I've only recently added — it lets you easily add dashboards and a query interface to a Ruby on Rails app, which I use to safely query my production data and track new signups, scanning health etc.

I've also landed on Chartkick, another Andrew Kane gem, for displaying charts. I was torn between using Chartkick and Vega for charts, but chose Chartkick for it's ability to toggle dataset visibility out-of-the-box by clicking on the legend. Vega (and Vega-lite) are probably the more versatile libraries overall though.

I use the Annotate gem in all my Ruby on Rails apps, and this is no exception. It adds schema annotations to the top of model files, which is handy info to have on hand. There seems to be a successor gem gaining some momentum, AnnotateRB, but I haven't looked into it.

Finally, I use the letter_opener gem to preview emails in development, which is a super handy quality-of-life improvement.

Conclusion

I hope you found this short article interesting!

Despite being an early-stage app, Affimon still has a lot of moving parts; I hope this look under the hood was insightful.