Yippee: Our vision for modern full-stack Ruby framework
What would it look like to build a new web application framework from a blank sheet of paper today? That’s the question Stephen Margheim and I were asking ourselves.
We’ve been writing Rails applications for years, and there’s a lot we like about Rails. But there’s also a lot of ways it could be improved and simplified.
Stephen and I realised we shared a similar taste in software design. What makes good software? What makes bad software? What we like about this. What we don’t like about that. What makes it fun and exciting?
So we decided to build a framework, with all the bells and whistles: models, views, controllers, routing, an object-relation-mapper (ORM), a query builder, database migrations, forms, background jobs, email, storage, deployment tools, notifications, a test framework and even authentication.
We’re approaching each layer with care and attention. What would make this ten times better, faster, simpler? We want a framework that’s so damn good people learn Ruby just to use it.
Current progress
We’ve already made significant progress in a few specific areas.
- Phlex is an incredible new way to build server-rendered HTML and SVG views. This will drive the server-side view layer in Yippee.
- Literal gives you a wonderful interface for defining object properties, with just the right level of runtime type-checking. I talked about it here.
- Quickdraw (WIP) is a super-fast test runner for Ruby with multi-core parallelisation. We use it to run tens of thousands of tests in just a few milliseconds.
- Plume (WIP) is a SQLite parser written in Ruby. This will drive a lot of database features related to migrations and query building.
- Canopy (private) is a routing-tree based router that will drive our controller layer. I talked about it here when it was still called “Yippee Router”.
- Refract is an AST-level Ruby rewriter that automatically generates source maps. We’re using this to build the upcoming compiler for Phlex.
- Strict Ivars raises when you reference an undefined instance variable. It works with Literal to help you avoid a whole category of bugs.
- Good Cop is a tasteful set of Rubocop rules optimised for auto-correct joy.
- Ruby Schema is a collection of JSON Schemas for Ruby-ecosystem YAML configs. This doesn’t directly relate to our work on Yippee yet. But I guarantee you if we end up with any YAML, JSON or TOML configuration files, they will have beautifully specified Schemas.
We’ve also made progress in other areas with various prototypes related to server management, deployment and query building.
What follows is an assorted set of ideas I’ve collected over time that I hope help you form a picture of what we’re aiming to do.
High-leverage
The entire framework should be small and simple enough to be maintained by just a few people. It should also be possible for one person to build, deploy and maintain a reasonably large Yippee app by themselves.
Everything should be simple.
Fast as hell
Yippee should be extremely fast. You should be able to boot the app in ~40ms. You should be able to run 10k tests and deploy to production in about one second.
The ORM should be fast, the view layer should be fast, migrations should be fast. You should be able to save any file and have the browser instantly reload with your changes, with no perceivable lag.
On the frontend, everything should be fast too with pages preloading on hover, or even when your pointer starts moving towards the link.
Beyond raw framework/application performance, every-day actions developers take should be fast. For example, we should optimise the shit out of how quickly you can jump from the browser to the relevant component or controller.
Multi-core performance
Both locally and in production, Yippee should utilise your machine’s maximum resources to deliver the best performance. Locally, the test runner should be properly parallelised. In production, workers spawned for the web server and job runners.
SQLite only
We’re focusing on SQLite as the only database. A SQLite deployment to a single machine can take you so much further than you think.
We’ll use SQLite for primary data, cached data and the job queue. Eventually, we’d like to support one database per tenant.
And using SQLite means we can lean into advantages like N+1 queries really being a feature rather than a bug, because there’s no latency between the app and the database.
Vertical scaling
You can get astronomical performance from one machine these days. For example, the AMD EPYC 9965 has 192 physical CPU cores with 384 vCPUs. On paper, the performance should be similar to ~48 Heroku Performance L Dynos.
By the time your app outgrows this, we’ll likely have even faster compute. And you don’t need to start there. You can serve thousands of customers on a $25 / month machine — that’s your database and everything.
Just enough types
Literal provides just the right level of type-checking. Types are checked at object boundaries rather than method boundaries, which makes them less intrusive than other type systems.
And because Literal type checks happen at runtime, you can describe any condition as a type without having to do type gymnastics.
When you have just enough types, your type system is your friend rather than your enemy. It keeps you honest, makes your test coverage more effective and helps you (and AI) know what’s what.
Minimal configuration
When you generate a Rails app, you end up with a bunch of configuration files. This makes upgrades difficult and also makes it hard to see exactly how you’ve customised your app.
Yippee should have little to no generated configuration. Want the default? Just remove that part of the config.
Routing tree (inspired by Roda)
A routing tree is not only fast, it’s also simple and high-leverage. Jeremy Evans talked about Roda’s routing trees here.
Our routing tree design naturally expands across multiple files or collapses into one file. You can have one class/file for each resource like Rails, or one class/file for each action like Hanami. It’s up to you.
Tooling
Yippee should integrate with developer tools: a RubyLSP extension; an MCP development server; HTML source maps for tools like Tidewave; queryable runtime documentation for dynamically defined methods and instance variables.
Integrated
Every component in Yippee should be beautifully integrated. For example, since we’re writing the test runner and the view layer, we can make view tests that don’t need to render, let alone parse any HTML.
All we really need to know is that we would have generated the HTML you were looking for.
Full stack
Web apps are part backend, part frontend. We build for both. We love CSS, TypeScript and Svelte as well as Ruby.
Autoloading
Autoloading with Zeitwerk is key to fast startup time. Yippee should be designed in a way to minimise how much code needs to be loaded up front.
Typically eager loading is preferred in production because it allows for better copy-on-write utilisation. However, with Pitchfork’s ability to reform a warm process, we might be able to achieve good CoW utilisation and autoload in production. See The Pitchfork Story by Jean Boussier.
Another thing we’ve been looking into is how to autoload entire gems. Most gems have an entry point that you need to require before you can use the gem. But if loading the gem doesn’t have side effects, and all access goes through a module, you could autoload gems themselves.
autoload :Money, "money"
Bootsnap
Deploying RubyVM instructions is faster than deploying source code so we should do that.
Svelte or Phlex or both
Single-page-apps are actually good now. And depending on what kind of application you’re building. Some apps work best as a SPA and we can build these with Svelte.
Other apps work better with a more traditional server-rendered model. We can use Phlex for that.
Most apps will be a bit of both. The majority of views are server-generated but sometimes they mount a small Svelte component for that one combobox, calendar or text editor.
Hybrid job runner
Because Yippee apps will be deployed to one server, there’s no need to use a Redis database for job queues. Jobs should be runnable immediately (blocking), in the background (non-blocking) or with an immediate blocking write to a SQLite queue followed by a non-blocking background runner.
Declarative migrations
You should be able to declare the schema you want and we’ll write the migrations for you.
Mapped databases names
A lot of names in the database are unnecessarily difficult to change. I think Yippee can solve this by presenting user-friendly names that map to fixed identifiers in the database.
Since you can’t connect to a SQLite database directly, the application layer will always be able to map the names on read/write.
And now you can “rename” things instantly.
Integrated deployment tools
You should be able to go from yippee new
to having an app in production with point-in-time database backups in just a couple of minutes. And the average time to deploy an to production after that should be about one second.
Yippee’s integrated deployment tools should let you scale your server up and down, restore from a backup and freely move between different hosting providers.
Frozen model layer
The model layer should be separate from the database layer. Models should be simple frozen objects, essentially Literal::Data
objects.
At the same time, it should be easy to sort of treat the model and the database layer as one with the complexity collapsing when there’s a straightforward mapping between model and table.
Composable SQL
Every possible SQL query can be represented as a Ruby hash, so SQL queries can compose just like Ruby hashes.
Exalt beautiful Ruby APIs
Yippee will use Ruby’s meta-programming capabilities to their fullest extent to make code that sings.
Embrace SQL queries in the view layer
Putting data-fetching right next to data-usage. The only reason we don’t do this in Rails is because it leads to N+1 database queries. But since these kinds of queries are an advantage with SQLite, we embrace them.
Playing the Accordion
We want to design APIs that smoothly expand and collapse as needed. A great example of this is found in the preview of the Canopy router here.
If this sounds interesting, please follow me and Stephen on Bluesky for updates. It’s going to be a few more years until you can use it, but we’ll get there.
Stephen talked about some of these ideas back in April. You can watch the video here.