Why We Don't Use TypeScript on the Backend
TypeScript is great for React components. It's overkill for a Rails API. Here's our take on where types help and where they just add noise.
Our frontend is TypeScript. Strict mode. No any types. Proper interfaces for every API response. We’re true believers when it comes to typing React components, and we’ll argue with anyone who says otherwise.
Our backend is Ruby. No types. No Sorbet. No RBS. Just plain Ruby with Rails conventions and a solid test suite. And we’ll argue with anyone who says that’s wrong, too.
This isn’t inconsistency. It’s choosing the right tool for the right layer.
Where Types Shine
The frontend is where TypeScript earns its keep. A React application is fundamentally about data flowing through components. Props go in, JSX comes out. When you have 200 components passing data to each other, types are the difference between confidence and chaos.
Without TypeScript, refactoring a shared component is terrifying. You rename a prop, and you have no idea what breaks until runtime. With TypeScript, the compiler tells you every callsite that needs updating before you even save the file. That’s not ceremony — that’s a safety net that pays for itself on day one.
API responses are the other obvious win. When your backend returns a JSON blob, TypeScript interfaces ensure your frontend handles it correctly. Missing field? Compile error. Changed field name? Compile error. The contract between frontend and backend is enforced by the compiler instead of by hope.
Where Types Add Noise
The backend is a different story. Rails is a convention-driven framework. The conventions are the type system.
When you write has_many :invoices in a Rails model, Rails knows the invoices table exists, knows the foreign key is user_id, knows how to generate the SQL, and gives you methods like user.invoices.where(status: :sent). Adding a type annotation to this doesn’t make it safer — Rails already handles it. Adding a type annotation makes it slower to write and harder to read.
Ruby’s duck typing isn’t a weakness. It’s a design choice that prioritizes developer speed. When I write a service object that takes anything responding to #call, I don’t need to define an interface. I don’t need a generic type parameter. I just write the code, and if I pass the wrong thing, my test suite catches it in 4 minutes.
That’s the key: tests replace types on the backend. Our Rails test suite covers every endpoint, every service object, every edge case. It runs in 4 minutes on CI. A failing test tells me exactly what’s wrong in a way that a type error sometimes doesn’t — because a type error tells me the shape is wrong, but a test tells me the behavior is wrong. Behavior is what matters.
The TypeScript-Everywhere Trap
There’s a movement to use TypeScript everywhere. Full-stack TypeScript. One language, one type system, shared types between frontend and backend. It sounds elegant.
In practice, it means choosing Node.js or Deno for your backend, which means giving up Rails. Giving up Rails means giving up ActiveRecord, Action Mailer, Solid Queue, Solid Cache, Turbo, Stimulus, and twenty years of battle-tested conventions that let a small team move fast. We’re not doing that to avoid learning two languages.
The developers who advocate TypeScript everywhere are often coming from JavaScript backends that genuinely needed types because JavaScript’s implicit coercion and lack of conventions made everything unpredictable. Fair enough. But Ruby isn’t JavaScript. Rails isn’t Express. The problem TypeScript solves on the backend barely exists when your framework does the heavy lifting.
Our Rule
TypeScript where data flows through many hands — the frontend. Ruby where conventions and tests provide the guardrails — the backend. No ideology. Just pragmatism.
The right answer to “should we use TypeScript?” isn’t yes or no. It’s “where?”
Vipul A M
Co-founder at Saeloun. Building Miru. Rails contributor. Shipping from Pune, India.
Read next
We Run a Monolith and We're Not Sorry
Microservices are a scaling solution for organizations, not technology. Here's why a Rails monolith serves Miru perfectly.
How We Track Time with AI Agents and the Miru CLI
A practical guide to automated time tracking for teams using Claude Code, Codex, and other AI coding tools. Real workflows, real scripts, zero browser tabs.
Why Your Time Tracking Tool Should Be Open Source
Vendor lock-in, surprise pricing, and disappearing features. Here's why open source time tracking isn't just nice to have — it's the only sane choice.
The article is the theory. Miru is the workflow.
If this post is about better billing, cleaner tracking, or fewer tools, Miru is the product version of that argument.