<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ryan Dooley</title>
    <description>The latest articles on DEV Community by Ryan Dooley (@doolay).</description>
    <link>https://dev.to/doolay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4056777%2Fb9caa66f-bb4d-474e-aa2e-cb3638e96b19.png</url>
      <title>DEV Community: Ryan Dooley</title>
      <link>https://dev.to/doolay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/doolay"/>
    <language>en</language>
    <item>
      <title>User Registration Is Never Just a Form</title>
      <dc:creator>Ryan Dooley</dc:creator>
      <pubDate>Wed, 05 Aug 2026 14:36:59 +0000</pubDate>
      <link>https://dev.to/doolay/user-registration-is-never-just-a-form-2hb5</link>
      <guid>https://dev.to/doolay/user-registration-is-never-just-a-form-2hb5</guid>
      <description>&lt;p&gt;"Add user registration" sounds like a checkbox, but what does that actually entail? What does a registration flow really need to hold up: not just accept a signup, but keep bad actors out, keep passwords safe, and make sure the person on the other end is who they say they are?&lt;/p&gt;

&lt;h2&gt;
  
  
  Even the form isn't free
&lt;/h2&gt;

&lt;p&gt;Before any of what follows, there's the form itself: two fields, submit button, done, right? Except now it needs styling that matches the rest of your app, inline validation so a bad email address or a too-short password doesn't round-trip to the server before the user finds out, and error states that don't look like an afterthought. None of that is hard. It's just real time, before you've stored a single user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The form works. Now you need somewhere to put that user
&lt;/h2&gt;

&lt;p&gt;That row can't just land in a table as-is. The password can't sit there as plain text, and it can't be encrypted either (encryption is reversible if you have the key, which means a breach hands over every password on your system). It has to be hashed: a one-way fingerprint you compare against, never reverse. And you shouldn't write that hashing yourself. It needs to be deliberately slow and resistant to brute-forcing, which is exactly the kind of non-obvious property a purpose-built library gets right and a homegrown one doesn't. On the Rails side, Devise handles this for you via bcrypt. On the Go side, I'm using &lt;code&gt;golang.org/x/crypto/bcrypt&lt;/code&gt;, same algorithm, same reasoning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The user's stored, but you don't know it's really them
&lt;/h2&gt;

&lt;p&gt;Anyone can type an email address into a form. So you need email verification, both to confirm the address is real and to keep bots from mass-registering accounts. Which means you need to be able to send an email. Which means you need an SMTP setup before you've built anything a user will ever see.&lt;/p&gt;

&lt;p&gt;Locally, I'm using &lt;strong&gt;Mailpit&lt;/strong&gt;, a real SMTP server in Docker with a local inbox UI. If you're coming from Rails, your instinct is probably Letter Opener instead, and it's genuinely easier to drop into a Rails-only app. I went with Mailpit because it's language-agnostic: the exact same setup works for the Go template too, one tool instead of two. In production, that email goes out through &lt;strong&gt;Resend&lt;/strong&gt;, mostly because the free tier is generous enough that I don't have to think about it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending it can't block the request
&lt;/h2&gt;

&lt;p&gt;You can't fire that email synchronously inside the registration request, or the user's signup hangs on a third-party API call. Invisible in dev, painful the first time Resend has a slow moment in production. So sending has to happen off to the side.&lt;/p&gt;

&lt;p&gt;On the Rails side, that's &lt;strong&gt;Solid Queue&lt;/strong&gt;, which hooks straight into Postgres, no Redis required. On the Go side, there's no equivalent convention to reach for, so I kept it simple: a &lt;strong&gt;goroutine&lt;/strong&gt;. No persistence, no retry, if the process dies mid-send the email's just gone. Not the "proper" answer, but for a fire-and-forget verification email at this stage, that's a fine tradeoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  The link works, until Gmail gets to it first
&lt;/h2&gt;

&lt;p&gt;The user has to click the link to verify. Simple, right? Build the endpoint as a plain &lt;code&gt;GET&lt;/code&gt;: click the link, hit the server, mark the account verified. Works fine in every manual test you'll ever run.&lt;/p&gt;

&lt;p&gt;Then a real user with a Gmail account signs up, and their account gets marked verified &lt;em&gt;before they've clicked anything.&lt;/em&gt; Gmail's link-scanning pre-fetches links in incoming email to check for phishing and malware, which means it hits your GET endpoint itself, and if that endpoint mutates state, Gmail just verified the account on the user's behalf.&lt;/p&gt;

&lt;p&gt;So the endpoint has to be a &lt;code&gt;POST&lt;/code&gt; instead. Gmail's scanner won't submit forms, only fetch links, which closes the hole, but it also means you can't just have a bare "click here" link anymore. Now you need a landing page with a confirm button that fires the POST. A small UI you didn't budget for until you hit this.&lt;/p&gt;

&lt;p&gt;That's registration for a single method. One more thing worth naming: today, users also expect to sign up with Google or GitHub, so there's another users table decision buried in here too, making sure it can hold whatever an OAuth provider hands back, and still make sense for someone who signs up with Google today and adds a password next month.&lt;/p&gt;

&lt;h2&gt;
  
  
  And that's it. That's registration.
&lt;/h2&gt;

&lt;p&gt;A users table that stores passwords correctly, and holds up for OAuth users too. A local email tool and a production email provider, wired up the same way. A queue or a goroutine so signup doesn't hang on a network call. A landing page and a POST endpoint just so Gmail doesn't verify accounts on your users' behalf.&lt;/p&gt;

&lt;p&gt;You still haven't built login, sessions, or password reset.&lt;/p&gt;

&lt;p&gt;If you'd rather not build all of that yourself, it's already solved in the templates over at &lt;a href="https://rdooley.dev" rel="noopener noreferrer"&gt;rdooley.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>rails</category>
      <category>go</category>
    </item>
    <item>
      <title>Why I Built Templates for Rails and Go (After 10 Years of Shipping Other People's Products)</title>
      <dc:creator>Ryan Dooley</dc:creator>
      <pubDate>Fri, 31 Jul 2026 14:40:01 +0000</pubDate>
      <link>https://dev.to/doolay/why-i-built-templates-for-rails-and-go-after-10-years-of-shipping-other-peoples-products-31pj</link>
      <guid>https://dev.to/doolay/why-i-built-templates-for-rails-and-go-after-10-years-of-shipping-other-peoples-products-31pj</guid>
      <description>&lt;p&gt;&lt;small&gt;&lt;em&gt;Originally posted on &lt;a href="https://rdooley.dev/blog/why-i-built-rails-and-go-templates" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;I'll get an idea for a side project, get excited, and start coding. Then a few days in I hit the same point every time: the app works on my machine, and now I have to actually put it on the internet. And that's where I stall out, not because the idea is hard, but because I'm suddenly choosing between five different hosting platforms, each with a different price tag and a different set of assumptions about what I already know how to do.&lt;/p&gt;

&lt;p&gt;It's a strange spot to be stuck in, because I've spent the last decade as a software engineer, most of it deep in Ruby on Rails, with a lot of Go mixed in more recently. On paper this shouldn't be the hard part. But most of that time has been inside one large codebase, on one team, where a platform team handles deployment and I just push code. So the one skill I never had to build is exactly the one I need the moment I try to ship something of my own.&lt;/p&gt;

&lt;p&gt;Take Render versus something like DigitalOcean. Render makes deployment genuinely easy, but running a full app there costs a lot more than handling the server yourself. And handling it yourself means the other part of the wall: configuring the server, setting up a reverse proxy, the stuff that isn't hard exactly, but isn't something I touch every day either, so every time I do it I'm relearning it from scratch.&lt;/p&gt;

&lt;p&gt;None of that is really about skill. It's just that this isn't the muscle I use day to day, and every time I want to ship something of my own, I end up paying that relearning tax before I even get to the idea itself.&lt;/p&gt;

&lt;p&gt;I wanted to build something that closed that gap, for me first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second problem: too many ideas, not enough time
&lt;/h2&gt;

&lt;p&gt;The other thing pushing me here was more personal. I've got a handful of SaaS ideas I'd genuinely like to explore, not just as hobby side projects but as things I'd want to actually test in front of real users. The problem is that "test an idea" and "build a SaaS product" are supposed to be two different amounts of effort, and in practice they're not. Setting up auth, billing, a database, deployment, a landing page: that's real time, and I don't have enough of it to pay that tax for every idea I want to poke at.&lt;/p&gt;

&lt;p&gt;So the templates became a way to solve my own problem first. If I could build a foundation solid enough to trust, I could spend my limited side-project hours on the idea, not on re-plumbing the same infrastructure for the fifth time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rails and Go specifically
&lt;/h2&gt;

&lt;p&gt;The Rails side is straightforward. It's what I know best, and it's still one of the fastest ways to get a real product in front of users.&lt;/p&gt;

&lt;p&gt;The Go side has a different origin. At my day job, I ran into a genuine limitation of Ruby and Rails, a case where a Go service would have handled the job better than what we had. That sent me down a real rabbit hole of learning Go, and it stuck. I'll say clearly: these templates don't answer that original day-job problem. That was a specific, narrow case. What they do is give me (and hopefully other Rails devs) a low-stakes way to get curious about Go, without having to justify learning an entire new stack just to try it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "solid" means to me
&lt;/h2&gt;

&lt;p&gt;I'll be honest, I don't think these are the flashiest templates out there. But I built them to be genuinely solid: the kind of foundation I'd trust for my own SaaS ideas, with the features that actually matter for shipping something real, not just a demo. I didn't cut corners on the parts that are tedious but necessary just to make the launch date.&lt;/p&gt;

&lt;p&gt;This is also just one piece of a bigger thing I'm building, a few different income streams running in parallel, of which this is the one I'm most excited about right now. I'll be writing more here as I go, including the more technical decisions behind each template.&lt;/p&gt;

&lt;p&gt;If you're a strong engineer who's never quite gotten around to shipping your own idea, or a Rails dev curious about Go without wanting to commit to a full rewrite, this might save you some time.&lt;/p&gt;

&lt;p&gt;If that sounds like you, I've put together two starter templates, one for Rails and one for Go, that handle the deployment, auth, and infrastructure setup for you. You can check them out at &lt;a href="//rdooley.dev"&gt;rdooley.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>rails</category>
      <category>go</category>
    </item>
  </channel>
</rss>
