Show HN: Cot: a Rust web framework for lazy developers
(mackow.ski)110 points by m4tx 3 days ago
110 points by m4tx 3 days ago
Thanks a lot for this extensive feedback!
About performance: I agree, and I'm not even trying to make performance a priority in Cot. I mean, of course, it's nice to have an actual compiled language, but I think a bigger perk in using Rust is having *a lot* of stuff checked in compile time, rather than in runtime. This is something I'm trying to make the main perk of, and it is reflected in multiple parts in Cot (templates checked at compile time, ORM that is fully aware of database schema at compile time, among many others).
About JSX: I think that's the one I'll need to explore further. In my defense, the templating system Cot currently uses (Rinja) is much more expressive and pleasant to use than Django's, but admittedly, the core concepts are very similar. This one might be difficult to address because of an ecosystem of templating engines that is pretty lacking in Rust, but I'll see what I can do to help this.
About front-end integration: that's something that will be (at least partially) addressed no later than v0.2. Django REST Framework is a pain (mostly because it's never been integrated in Django), Django Ninja is something I haven't personally used very much - good to have it mentioned so it can be a source of inspiration. Generating OpenAPI docs is something that's even mentioned in the article "Request Handler API is far from being ergonomic and there’s no automatic OpenAPI docs generation" so yeah, I'm aware of this.
Deployment is indeed something that's super nice – and a part of this is that newer Rust versions generally don't break compatibility with existing code, unlike Python. I agree this should be highlighted, thanks for suggestion!
You could potentially address both templating and front-end integration by adopting Dioxus which does full stack rendering with React-like components (but in Rust). A "batteries included" full-stack framework could be quite exciting I think.
(Disclaimer: I work on dioxus's native renderer)
The post mentions an ORM, but the docs don’t provide any examples of how it works. How does the Cot ORM compare to Diesel? There are certainly places where I find Diesel falls short of, say, ActiveRecord (like traversing relationships via “includes”/“eager_load”/etc.). I’m not convinced that these shortcomings warrant a separate ORM as opposed to making contributions to Diesel, but I’m open to being wrong. Are there any particular shortcomings that motivated starting from scratch? One thing I really like about Rust dev is the way ORM and API server concerns are separated. Will Cot’s server support using Diesel? Can it be used without the ORM package being involved at all?
About the docs, there is a guide chapter dedicated to the ORM: https://cot.rs/guide/latest/db-models/
There are several reasons I don't like the existing ORMs in Rust, many of them being overly verbose, some generating unnecessary files, and having somewhat weird DSLs being the main reasons. The main reason, I think, was that none of them supports automatically generated migrations, and I absolutely loved that feature in Django. The differences between existing ORMs sound like a neat idea for another blog post, to be honest, so I'll probably do that soon!
Diesel absolutely can be used, there is no reason it can't - database support is feature flag-gated, so disabling it as as easy as adding one line in your Cargo.toml file. This, however, will obviously also disable some other features that depend on the database, such as authentication through database.
Whether building a custom ORM will be a good idea - only time will tell.
That's actually a very good point. I was thinking a lot about whether to make async a requirement. Given that sync code is often easier to reason about and debug and Cot tries to be developer-friendly, I'm leaning towards "sync by default, async when you need to", but I'll need to explore this more. Thanks for this thought!
There are two competing use cases for web frameworks. In embedded systems you might have one or two users for a web page, and the page exists because you don't want to put a display on the device. That's where a synchronous approach really shines because it's easy to reason about and therefore doesn't break very often. Contrast that with a traditional web site with a backend, frontend, and thousands or millions of users. In that case, async code is easier to reason about because each visitor probably isn't interacting with the same elements of your model at the same time, and if they are you can define how those interactions work and enforce synchronization using the database. So it's not an either/or, but rather two approaches that address totally different situations.
Usually non-async frameworks spawn some kind of thread so that it never blocks or crashes the main process regardless of what happens to the threads.
I know Go frameworks are usually like that. They spawn green threads and you can write HTTP handlers using simpler sync code to handle requests without worry.
That would require programming in python, which as everyone knows, is not Rust.
Leaving a mention of Tuono to those looking for new Rust web frameworks. It's closer to Next.js and allows fast server-side rendering of React apps:
I understand people use these logins, but I'm not trusting a third-party login to gate access to anything I might miss. It's already annoying enough that there are virtually no access guarantees for a service, but to add a second point of failure is a risk I'm not going to take if I have any fear of losing access. For something like TikTok where I can download the recipe videos the risk is appropriately low, but that's the only place I can think of off the top of my head where I actually use a login provider.
Plus, it offers very little convenience for users vs just using a password manager. I realize figuring out the right bcrypt/datastore setup is kind of annoying, not to mention opening you up to liability, but it's always been worth it to me in the past.
While I generally agree with your comment, a lot of people actually use it because they don't use a password manager and it's more convenient then.
One additional benefit to having OAuth2 support though is that you can easily set up SSO if needed (and you don't care about SAML or other SSO protocols). For me personally this will be useful for an NGO that I'm running where we have Google Workspace effectively serving as a Single Sign On server.
Yea, I'm not ragging on the parent for the choice they made. There's a lot of services it's just not worth the effort, and many people do find oauth2 to work just fine.
Good stuff, can't wait to see more features :)
Also, +1 for
> struct GownoApp;
another web framework for rust (nice)
I wonder how this par with something like https://loco.rs/
What's funny is its obviously in early stages, but its homepage doesn't show an immediate code sample. I find it funny because you are trying to appeal to lazy devs but making them work to get to any code samples.
The golden rule for making a website / readme for any programming language, web framework (or even GUI frameworks) is to have at least one or more code samples, so people can see what coding in your framework looks like.
I'll excuse it since it's a newish project it seems. :)
On that note, I'll be keeping a close eye on this one. This is the kind of web framework I look for, batteries included.
Cot's homepage:
Good call. Django's admin site functionality was the killer app for me, so if I'm going to hop off Django to something else then I'm going to want to compare that feature closely.
Frameworks that do “everything” are not a good idea. I’m from the Java ecosystem. Spring is the “batteries included” framework there. If you have migrated any real world application to a new major version once, you’ve learned forever that “all in one” frameworks are bad. Please don’t do it!
Instead, use scaffolding tools, that give you a head start on creating a new project, using smaller, specialized libs.
Also, don’t use an ORM. Just write that SQL. Your future self will be thankful.
> Also, don’t use an ORM. Just write that SQL. Your future self will be thankful.
I have never understood this. I've been using the ORM for twenty years now, it's saved me countless hours, and I've very rarely needed to break out of it. When I did, I just wrote an SQL query, and that was it. What's the big deal?
This depends very much on the size of the code base, the amount of data in the DB and (of course) the quality of the ORM.
It sounds like you are lucky enough that you have never had an ORM generating badly optimised/n+1/over-eager queries that take down a production service. Or perhaps had to debug low level query cache issues causing unexpected problems.
I'm not advocating for plain SQL, just offering some suggestions as to why someone might want you to consider it.
It depends on the ORM. I've definitely seen a couple (popular, even) where it's worse than nothing at all, if you're comfortable in SQL. But most of them I'd agree, they're handy.
I think some of the blanket "just don't" comes from people who've had to onboard to projects written by teams that didn't understand SQL, but did (sort of) know how to use an ORM, and blame the ORM for allowing those teams to commit their atrocities. But that doesn't make an ORM a bad thing in capable hands.
Every one of the three points you made in your comment has been disapproved by Django.
It's a framework that does (or at least tries) to do everything (or as much as possible), and it's good. Really, really good.
I'm in the process of upgrading an app of mine after months of abandonment, and the process is so smooth it's boring.
Also, Django's ORM is the closest thing to a perfect tool. Sure, it has some dark corners here and there, but Django's ORM has considerably improved my quality of life as a programmer. Heck, I even moved from SQLite to PostgreSQL in an old project, and all I needed to do is to change a couple of config code.
Oh, and Django is both stable and enjoys a constant pace of updates adding useful features practically at every major version.
Yeah. I can’t believe the alternatives that people try to glue together compared to Django.
Honestly Django has the best db migrations I have used from any language or library.
When I start a project, even if I am going to access the db from raw sql I start by modeling it in Django just because the rapid changes in data modeling allow me to not even think about it. I just have to think about what I want it to be and not about the changes to get there. Other ORMs or no ORM I am writing alter tables instead of focusing on the data model itself.
I program Rust using the Axum framework and Sqlx.
Github copilot is so good at writing CRUD db queries that it feels as easy as an ORM, but without the baggage, complexity, and the n+1 performance issues.
> The ORM is very lacking at the moment and the automatic migration generator only works with a small subset of possible operations,
I would have hoped that by 2025, new projects would have moved away from ORMs entirely. They really are a useless abstraction layer that always brings tons of trouble and usually makes queries harder to write.
Looking at the first example in the docs
https://cot.rs/guide/latest/db-models/#retrieving-models
let link = query!(Link, $slug == LimitedString::new("cot").unwrap()).get(request.db()).await?;
I really don't get the point, and that'll certainly get worse with any somewhat non-trivial query.Why go through all the trouble of reinventing a new DSL when the SQL equivalent would be so much cleaner?
SELECT * FROM link WHERE slug = 'cot';
Composability is the often cited benefit. As an example, I can do the following in Active Record (Ruby):
class Account < ApplicationRecord
scope :active, -> { where.not(active_at: nil) }
belongs_to :user
end
class User < ApplicationRecord
scope :born_before, ->(born_on) { where(birthdate: born_on) }
end
active_users_with_birthdays_today = Account.active.joins(:user).merge(User.born_before(Date.today))
Contrived, of course, but it's not hard to see how you can use these sorts of things to build up record sorting, filtering, etc. Doing this by hand wouldn't be very fun.The thought process can be the same in SQL. You can start by writing SELECT * FROM Account; then add your JOIN to User, then add your predicates. Then you can refine it – here, if I’m understanding the code correctly, you’re using User for a JOIN but never return anything from that table, so you could turn it into a semi-join (WHERE EXISTS) and likely get a speed-up.
> then add your predicates
How are you canonically storing these predicates in code to reuse over 5-10 queries?
By the time you have a query builder that will rewrite queries so that column name and table names to be compatible with composition you basically have an ORM.
Especially if you are defining types anyways to extract the data from the sql into.
Writing raw SQL is perhaps indeed easier for simple queries, but put some foreign keys inside or slightly more complex relationships between tables and you'll probably quickly fall into the trap of having to remember your entire database schema to write anything. Yes, the example from the documentation is slightly more complicated, but it checks at compile time the exact column names and types, so you get feedback much quicker than actually running the SQL query.
In addition to that, over the years of writing web services, I always found raw queries much less maintainable. The ORM immediately tells you that you messed up your refactoring, whether it is just renaming a column, or removing a table completely.
Pretty much every ORM (including the one in Cot) also allows you to write your own custom queries if the abstractions are not enough, so there's always this fallback option.
> Yes, the example from the documentation is slightly more complicated, but it checks at compile time the exact column names and types, so you get feedback much quicker than actually running the SQL query.
Well sqlx checks at compile time your raw query if you use their macro, at the expense of increased compile times.
I never understood the hate against ORMs. It always seems like a superiority complex or some claim that SQL is “so simple”.
Yeah SQL is extremely simple to write, that isn’t really the problem ORMs solve for me, but rather they solve composability and reuse of queries.
I think there is a similar vein of people crying about sites not using 100% vanilla js without a framework. They are missing the point or don’t have enough experience with the actual problems the abstractions solve.
This is one of my biggest pet peeves against HN. It's also obvious ORMS won, every new framework creates them. People who keep speaking about not using them are crazy.
I've yet to see an ORM which doesn't just eventually make everything harder. Especially when it comes to automatic migrations, which seldom seem to take into account production necessities like multi-node deployment, downtime considerations and not destroying your production data silently because it doesn't fit.
Exact opposite experience.
“SQL strings are one of those things that make sense for really tiny projects but fail to scale once complexity settles in“
Large projects require reuse, composability and easy refactoring. All things ORMs excel at.
On a small code base it is easy to rename a column or add a column, etc.
On a large code base with already 100s of queries using that table, without an ORM it isn’t as straightforward to update all references and ensure that ever place consuming that table has the new column info.
> like multi-node deployment, downtime considerations
Never had an issue with Django on a large project at a previous $8b startup whose code base went over several major data refactors.
In fact Django’s excellent migrations was specially called out for the reason for our confidence in making large DB refactors.
It's 2025, and some ORMs are quite good (eg. Django, ActiveRecord). You can have an ORM with escape hatch of SQL. I do not understand the people who are so against ORMs.
I use ORM query interfaces 80% of the time, and for performance critical queries I can use direct SQL. But the query sanitization, smart preloading and performance checks, schema management and migration tools they often provide, and type checks / hints are really nice features of ORMs.
Furthermore, ORMs are ESPECIALLY useful in teams where you have more junior devs who need these guardrails.
I think the issue here is trying to reinvent a new ORM.
As long as it's still possible to also use SQL queries with it it's fine, right? I know some people who prefer using ORMs and some who like writing SQL.
It’s not fine because ORMs never make sense as an abstraction layer. They try to map concepts that are inherently incompatible. Relational databases are designed around set theory and strict schema constraints, while object-oriented programming relies on hierarchical structures and mutable state.
Well just because it uses the word object doesn’t mean it is strictly for object oriented programming.
Haskell and Elixir, etc all have ORMs (or rather ecto calls itself a data mapper but functionally it is 1:1 analogous to an ORM in any other language)
> I don't think it's a good idea to have a mix of both in a codebase, because then you'll have to be good at both - kind of defeats the purpose.
When using an ORM you already have to be good at both for any nontrivial query.
My impression is people use ORM not because they lack SQL skills (which is possible), but because it makes object-mapping much easier and with a large count of tables and ever-evolving database, at some point it just feels more natural to drop writing SQL queries and rely solely on the ORM to build up your object graph.
I actually find myself on crosspath where I did start with just SQL but now the database has grown a lot and it is just too much effort to keep writing queries as features pile up. Switching to ORM in one take would be nearly impossible but probably it could work as a step-by-step process, so have both SQL and ORM. Still thinking about this.
> Also, people like ORMs. Type safety is nice too without having to map manually. Sqlx is also great
Don't you get type safety anyway with parameterised parameters?
ORMs, in my experience, fail the hardest when DB admins and web devs butt heads in when and where to create models. It can be an extra hurdle for data flexibility, but it clarifies data architecture at the beginning. In our Django environment, the DB admins won and we added `MIGRATE = False`.
I never worked with Rust, but maybe the complexity is due to how the language works.
Using Django's ORM, that would simply be: link=Link.objects.get(slug="cot")
It'd still be very readable and manageable even for complex queries.
ORMs are great. They afford abundant opportunities to look like a hero by swooping into a project and cutting a page load of forty seconds to under one second.
if you can decompose the query, you can unit test the logic against inmemory sets(or language specific equivalent).
What is the competitive landscape looking like these days for Rust web frameworks? I couldn't help but feel a bit insecure last time I was exploring the various options because none of them seemed to have the maturity or longevity of frameworks from other languages (for obvious reasons).
Is there one framework that stands out from the rest from an "investment risk" perspective? In other words, if my company is going to choose a framework to build a website, which one has the lowest odds of burning me by becoming abandoned or unsupported?
I would say pick Axum or Actix, these are the go to right now with lower risk to be abandoned, But they aren't batteries included. Here is a list of blessed[0] libraries that might help you to choose the most popular ones in their respective category, but at the end depends on you to pick the one that has the biggest community.
My go to is Axum + sqlx most of the time.
Axum is most likely to be supported long term since it has quite a bit of support and is a Tokio project but it's not nearly as batteries included as something like Rails or Django. I doubt any of the current batteries included Rust web frameworks will last.
Lots of “Rails but for X” projects exist in various states, for various languages, but have failed to gain traction and stand abandoned.
…like, a lot.
I thiiiink, fundamentally, it’s a harder-than-it-looks problem space and if you come out with something that’s rough and broken, it never gains enough critical mass for it to become self sustaining.
What’s the “pitch”? It’s can’t be “Django but for rust but it’s kind of not ready yet”, bluntly.
So it needs to have some outstanding feature or accumulation of features that let it stand out from the other 30 rust web frameworks. Is there something here?
You really need to call out the “why another rust web framework” in more than just terms of ambition, imo.
Yeah, that's something I'm painfully aware of. Part of the reason I'm starting to push hard even though it's not ready yet is to gain as much feedback as early as possible to avoid expensive mistakes in the long run. Is that a mistake – I don't know, we'll see.
The main selling point for now is the admin panel and the custom ORM that handles automatic migration generation. There are a lot of finer differences as well, and I'll add a comparison table soon since that's something I'm (expectedly) getting asked about quite frequently.
Ok(Response::new_html(StatusCode::OK, Body::fixed(rendered)))
no thank you. if you like it good for you, for me this looks worse than
Body body = BodyFactory.newInstance(BodyConfiguration.OK)
which is still SHORTHER and less cancer
Your BodyFactory example isn't even functionally the same as the quoted code snippet. Nowhere is the Body::fixed(rendered) equivalent seen, for example.
The Rust ecosystem needs more high-level frameworks like this. However, I've been shipping Django since 0.96, and I don't think Cot really addresses the main issues Django currently has. Performance isn't in the top 5.
Django's biggest issue is their aging templating system. The `block`, `extend` and `include` style of composition is so limited when compared to the expressiveness of JSX. There are many libraries that try to solve Django's lack of composition/components, but it's all a band-aid. Today, making a relatively complex page with reusable components is fragile and verbose.
The second-biggest issue is lack of front end integration. Even just a blessed way of generating an OpenAPI file from models would go a long way. Django Ninja is a great peek at what that could look like. However, new JS frameworks go so much further.
The other big issue Django has _is_ solved by Cot (or Rust), which is cool (but not highlighted): complicated deployments. Shipping a bunch of Python files is painful. Also, Python's threading model means you really have to have Gunicorn (and usually Nginx) in front. Cot could have all that compiled into one binary.