base on Opinionated template for starting new web applications with Ruby on Rails and Hotwire [![Build Status](https://github.com/templatus/templatus-hotwire/workflows/CI/badge.svg)](https://github.com/templatus/templatus-hotwire/actions) # Templatus (Hotwire edition) Templatus is an opinionated template to build web applications with Ruby on Rails and Hotwire. It simplifies the process of setting up a new application while following best practices. Live demo available at https://templatus-hotwire.ledermann.dev ## Similar projects There are two sister repositories: - For using Vue.js instead of Hotwire: https://github.com/templatus/templatus-vue/ - For using Inertia and Svelte.js instead of Hotwire: https://github.com/templatus/templatus-inertia/ ## Features / Technology stack ### Backend - [Ruby](https://www.ruby-lang.org/de/) 3.4 - [Ruby on Rails](https://rubyonrails.org/) 8.1 - [ActionCable](https://guides.rubyonrails.org/action_cable_overview.html) for WebSocket communication - [PostgreSQL](https://www.postgresql.org/) for using as SQL database - [Sidekiq](https://sidekiq.org/) for background processing - [Redis](https://redis.io/) for Caching, ActionCable, and Sidekiq ### Frontend - [Hotwire](https://hotwired.dev/) for building the frontend without using much JavaScript by sending HTML instead of JSON over the wire - [TypeScript](https://www.typescriptlang.org/) for static type checking in Stimulus controllers and other script code - [ViewComponent](https://viewcomponent.org/) for building reusable, testable & encapsulated view components - [Tailwind CSS 4](https://tailwindcss.com/) to not have to write CSS at all - [Heroicons](https://heroicons.com/) for beautiful hand-crafted SVG icons - [Vite](https://vitejs.dev/) for bundling JavaScript and CSS with Hot Module Replacement (HMR) in development ### Development - [Puma-dev](https://github.com/puma/puma-dev) for using .test-domain and HTTPS in development - [Overmind](https://github.com/DarthSim/overmind) for starting up the application locally (Procfile handling like Foreman) - [dotenv](https://github.com/bkeepers/dotenv) to load environment variables from .env into ENV - [Prettier](https://prettier.io/) for auto-formatting JavaScript code in Visual Studio Code - [SyntaxTree](https://github.com/ruby-syntax-tree/syntax_tree) for auto-formatting Ruby code in Visual Studio Code - [Lookbook](https://github.com/allmarkedup/lookbook) as development UI for ViewComponent - [annotaterb](https://github.com/drwl/annotaterb) for annotating models and routes - Live reloading ### Linting and testing - [RuboCop](https://rubocop.org/) for Ruby static code analysis - [ESLint](https://eslint.org/) for JavaScript static code analysis - [RSpec](https://rspec.info/) for Ruby testing - [Factory Bot](https://github.com/thoughtbot/factory_bot) for setting up Ruby objects as test data - [Capybara](https://github.com/teamcapybara/capybara) with [Playwright](https://playwright.dev/docs/intro) for system tests ### Deployment - [Docker](https://www.docker.com/) for production deployment, NOT for development - [DockerRailsBase](https://github.com/ledermann/docker-rails-base) for fast building an optimized Docker image based on Alpine Linux - [GitHub Actions](https://docs.github.com/en/actions) for testing, linting, and building Docker image - [Dependabot](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/about-dependabot-version-updates) configuration for updating dependencies (with auto-merge) - Ready for serving assets via CDN like CloudFront - [Honeybadger](https://www.honeybadger.io/) for error tracking in Ruby and JavaScript - [RorVsWild](https://www.rorvswild.com/) for performance monitoring - [Plausible](https://plausible.io/) for privacy friendly analytics - [Lockup](https://lockup.interdiscipline.com/) to place a staging server behind a basic codeword ### Production - [Lograge](https://github.com/roidrage/lograge) for single-line logging - Gzip and Brotli compression of dynamic responses (HTML, JSON) using [Rack::Deflater](https://github.com/rack/rack/blob/master/lib/rack/deflater.rb), [Rack::Brotli](https://github.com/marcotc/rack-brotli) - Fine-tuned Content Security Policy (CSP) - Ready for PWA (manifest, service-worker) ## Metrics This template is developed with optimized performance and security in mind. The following benchmarks are performed against the demo installation on production. It uses an inexpensive virtual server on the [Hetzner Cloud](https://www.hetzner.com/de/cloud) behind a [Traefik](https://traefik.io/traefik/) setup. ### Lighthouse site performance 100% in all categories. ![Lighthouse](docs/lighthouse.png) ### Security headers [![Security headers](docs/security-headers.png)](https://securityheaders.com/?q=templatus-hotwire.ledermann.dev&followRedirects=on) What's the red _Permissions-Policy_ badge? This seems to be fixed with one of the next Rails update: https://github.com/rails/rails/pull/41994 ### Mozilla Observatory [![Mozilla Observatory](docs/mozilla-observatory.png)](https://observatory.mozilla.org/analyze/templatus-hotwire.ledermann.dev) ### WebPageTest [![WebPageTest](docs/web-page-test.png)](https://www.webpagetest.org/result/220530_BiDcQQ_83b754931114d99469b521e72731eb4d/) ### GTmetrix [![GTmetrix](docs/GTmetrix.png)](https://gtmetrix.com/reports/templatus-hotwire.ledermann.dev/rSqolxsw/) ### Check-your-website [![Check-your-website](docs/check-your-website.png)](https://check-your-website.server-daten.de/?q=templatus-hotwire.ledermann.dev) ### JavaScript size 159 KB of compiled JavaScript (minified, uncompressed). The largest parts are: - Turbo with ActionCable (72 KB) - Stimulus (32 KB) - Honeybadger (25 KB) ``` $ RAILS_ENV=production bin/rails assets:precompile ➤ YN0000: · Yarn 4.4.1 ➤ YN0000: ┌ Resolution step ➤ YN0000: └ Completed ➤ YN0000: ┌ Fetch step ➤ YN0000: └ Completed ➤ YN0000: ┌ Link step ➤ YN0000: └ Completed ➤ YN0000: · Done in 0s 312ms Building with Vite ⚡️ vite v5.4.3 building for production... transforming... ✓ 47 modules transformed. rendering chunks... computing gzip size... ../../public/vite/.vite/manifest-assets.json 0.10 kB │ gzip: 0.09 kB ../../public/vite/assets/logo-DdqaqAN0.svg 0.50 kB │ gzip: 0.30 kB ../../public/vite/.vite/manifest.json 0.57 kB │ gzip: 0.22 kB ../../public/vite/assets/application-Buqxuzrp.css 23.64 kB │ gzip: 5.07 kB ../../public/vite/assets/application-CFMovW7N.js 3.64 kB │ gzip: 1.44 kB │ map: 9.72 kB ../../public/vite/assets/vendor-CjM9kOWQ.js 208.59 kB │ gzip: 57.68 kB │ map: 715.99 kB ✓ built in 1.03s Build with Vite complete: /Users/ledermann/Projects/templatus-hotwire/public/vite ``` ### Network transfer Small footprint: The demo application transfers only **62 KB** of (compressed) data on the first visit. ![Network](docs/network.png) ### Docker build time With multi-stage building and using [DockerRailsBase](https://github.com/ledermann/docker-rails-base) the build of the Docker image takes very little time. Currently, the build job requires about 1,5 minutes on GitHub Actions (see https://github.com/templatus/templatus-hotwire/actions) ### Docker image size The Docker image is based on Alpine Linux and is optimized for minimal size (currently **117 MB** uncompressed disk size). It includes just the bare minimum - no build tools like Node.js, no JS sources (just the compiled assets), no tests. ``` $ container-diff analyze ghcr.io/templatus/templatus-hotwire -n -----Size----- Analysis for ghcr.io/templatus/templatus-hotwire: IMAGE DIGEST SIZE ghcr.io/templatus/templatus-hotwire sha256:... 116.7M ``` ## Getting started ### Install for development 1. Clone the repo locally: ```bash git clone [email protected]:templatus/templatus-hotwire.git cd templatus-hotwire ``` 2. Install PostgreSQL, Redis, and puma-dev (if not already present). On a Mac with HomeBrew, run this to install from the `Brewfile`: ```bash brew bundle ``` 3. Install and set up [puma-dev](https://github.com/puma/puma-dev) to use HTTPS for development. Do this on macOS: ```bash sudo puma-dev -setup puma-dev -install puma-dev link # Use Vite via puma-dev proxy # Adopted from https://github.com/puma/puma-dev#webpack-dev-server echo 3036 > ~/.puma-dev/vite.templatus-hotwire ``` 4. Setup the application to install gems and NPM packages and create the database: ```bash bin/setup ``` 5. Start the application locally: ```bash bin/dev ``` Then open https://templatus-hotwire.test in your browser. ### Preview components in LookBook ```bash bin/dev ``` Then open https://templatus-hotwire.test/lookbook/ in your browser. ### Running linters RuboCop: ``` bin/rubocop ``` ESLint: ``` bin/yarn lint ``` ### Running tests locally Ruby tests: ``` bin/rspec open coverage/index.html ``` System tests with Capybara and Playwright: ``` bin/rspec spec/system ``` ### Test deployment locally ``` docker network create public docker compose up ``` ", Assign "at most 3 tags" to the expected json: {"id":"6332","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"