DEV Community

Rails Designer
Rails Designer

Posted on • Originally published at railsdesigner.com

Using Subdomains in Rails: Development vs Production

This article was originally published on Rails Designer's build a SaaS series


For a current SaaS I am working on I needed to set up a webhook endpoint. I want the production environment to use a subdomain like webhooks.helptail.com, but during development, this needs to work on localhost:3000. So I used some of Rails' routing features for a solution that adapts based on the environment.

The Challenge with Subdomains Locally

When developing locally, Rails applications typically run on localhost:3000, which doesn't support subdomains in the same way as (development) domain name, like a .dev TLD.

To solve this problem, a different routing strategy is needed for development versus production environments.

Here's a clean solution that handles both scenarios elegantly:

# config/routes.rb constraints Rails.env.production? ? {subdomain: "webhooks"} : {} do scope Rails.env.production? ? {} : {path: "webhooks"} do post "/:path", to: "workflow/webhooks#create", as: :webhooks end end 
Enter fullscreen mode Exit fullscreen mode

This code block does two things:

  1. Production: routes requests to the webhooks subdomain; example: https://webhooks.helptail.com/a-unique-path
  2. Development/test: adds a /webhooks prefix to the path; example: http://localhost:3000/webhooks/a-unique-path

The “magic” happens through the conditional use of Rails' constraints and scope methods. The constraints method applies the subdomain restriction only in production, while the scope method adds the path prefix only in development.

Using separate subdomains for APIs and webhooks (like webhooks.helptail.com) creates a foundation for growth. This approach lets you scale high-traffic parts of your app independently when customer usage surges, implement targeted security policies without complicating your main application (eg. dashboard.helptail.com), and establishes an architecture that simplifies future migrations as you expand (globally through CDNs).

This same pattern works great for API endpoints too. Many applications use an api subdomain:

constraints Rails.env.production? ? {subdomain: "api"} : {} do scope Rails.env.production? ? {} : {path: "api"}, defaults: {format: :json} do namespace :v1 do resources :users resources :posts end end end 
Enter fullscreen mode Exit fullscreen mode

This creates endpoints like:

  • Production: https://api.example.com/v1/users
  • Development: http://localhost:3000/api/v1/users

What do you think of this solution?

Top comments (0)