About Rat
Small web apps should feel small.
Rat is a tiny language where one .rat file can hold a server, a page, styles, and behaviour, all served by one Go binary.
one .rat file · one Go binary · one dispatch shape
Why I made Rat
Rat started as a personal tool. I wanted to build small websites and apps without turning every idea into a stack decision first.
The shape came from borrowing small pieces of tools I liked: Ruby, Python, Pug, JSX, SCSS, Tailwind, C#, and a few others. Rat does not copy those languages whole. It keeps the parts that make web code feel direct and gives them one model.
The goal was modest: open one file, see the server state and the page, make a change, and still understand the code when I came back later.
What makes Rat unique
Rat gets most of its character from a few constraints.
- One file, one binary. A route can carry its server state, markup, styles, and behaviour in one
.ratfile. The basic workflow needs no package manager, bundler, or app server beyond the Rat binary. - One dispatch primitive. Every callable has the same shape:
name.method(args). Built-ins, services, database handles, JSON APIs, Python modules, and browser JS libraries all go through the same idea. - Tail and Inverse Tail. Tail lets Rat call another runtime, such as Python, through that same shape. Inverse Tail lets page-side Rat call JavaScript that already lives in the browser.
- Signed page calls. Each page renders with the exact calls it may make, tied to the route and session. Anything outside that list is rejected before it runs.
- Live edits. Save a file and the browser updates. The feedback loop stays short because the moving parts stay few.
Why Rat exists
Modern frameworks are good. The problem is that small apps often inherit big-app ceremony.
Before the first button works, you may already be choosing a language mode, a UI library, a router, a bundler, a server layer, an API shape, and a state model. None of those choices is wrong on its own. The pile is the problem.
Rat pushes the other way. It keeps the web-facing surface small, regular, and easy to explain.
I do this too. I copy old projects, keep stale conventions, and wrap old decisions in new names. Give it six months and I can open my own code feeling like I need a guided tour. Rat is an attempt to avoid that drift.
Everything else
Rat
Complexity over time. Same axis on both sides.
Rat does not try to become every language. It gives you a small web-facing layer, then lets Python, JavaScript, SQL, or another runtime handle the parts they already do well.
The feeling Rat is after
“Good tools leave more attention for the thing you are making.
Rat in practice
State, markup, and behaviour usually live in separate places, with glue between them. In Rat they can share one file and one connection model. Here is a whole counter: the code on the left, the running thing on the right. Same source.
counter.rat
> server
count: 0
> page
<button on_click[count << count + 1]> clicked [count] times running, live
One mental model
Rat fights entropy by being a little stubborn. One way to declare state. One way to render it. One way to mutate it. One call shape for everything: name.method(args). The other end might be a built-in, a database, a service, a JSON API, a Python function, or browser JavaScript. The shape stays the same.
One model does not make every app simple. It keeps the language from adding new spellings for old ideas. Code stays easier to read, easier to review, and less annoying to return to later.
Same call, either side
In most stacks, the server and browser feel like different worlds. Rat keeps pure computation consistent across both. Built-ins for strings, math, collections, regex, dates, JSON, encoding, and logging have Go and JavaScript versions, so the same call can behave the same during a server render and a browser update.
Effects stay where I/O belongs. Databases, HTTP, services, Tail, channels, and native APIs commit to a host and pass through dispatch when needed. The line is explicit instead of hidden in framework glue.
AI should read the language, not guess it
New languages do not have a giant training corpus. Rat leans into that. The language is small enough to describe directly, so a model can work from the rules instead of guessing from whatever stack it has seen most.
If you write Rat with an LLM, start with llms.txt. For code generation, give it llms-full.txt: the complete dialect guide and docs in one file.
That is also a useful constraint on Rat itself. If the language cannot be explained clearly, it is probably getting too complicated.
What Rat is for
Rat is not a replacement for mature production stacks. Use those when you need their ecosystem, scale, and years of battle-testing.
Rat is for small apps, personal tools, prototypes, teaching examples, and experiments where the whole program should fit in your head.
The bet is ordinary, but worth making: some software can stay small. Tools can have less ceremony. Building things can still feel like making.
→ Read the docs→ Build something
I am Angelo Wolff, and I designed and built Rat. The source lives on GitHub, and you can find more of my work on my profile.