Welcome, Cot: the Rust web framework for lazy developers

Ever wanted a Django-like experience in Rust? Meet Cot, a batteries-included web framework designed for developers who just want to get things done.

It has been built from a frustration that there is no easy-to-use, fully features web framework for Rust, even though the web development ecosystem has existed for quite a long time in the community. It builds upon projects such as axum, sea-query, tower, serde, and more, combining them in a package that allows you to start quickly, adding a lot of features in the process.

Cot comes with built-in authentication, sessions, an admin panel, templates, and even its own ORM with automatically generated migrations – something that even the most established ORMs in the wild (such as SeaORM and Diesel) do not provide. It is still in early development and hence it's still missing many features and is by no means production-ready yet , but we're planning to make frequent updates to close the gap to other mature tools as quickly as possible!

We need your feedback, contributions, and ideas to shape its future! See the introductory blogpost, or go directly to the official webpage, or the GitHub repository to start building with Cot!

4 Likes

Congratulations to the release. Designing and implementing such a framework is impressive work.

That written I would like to point out that your differentiating property to established ORM's like diesel is just not correct. You write that your ORM comes with automatically generated migrations, but no other ORM in Rust has this feature. That's not correct, as Diesel supports the same feature for several years nows. See our Getting Started Guide for how to use that feature.

1 Like

Thanks a lot!

About migrations: this might not have been written clearly in my article, but there are two key differences between Cot's automatically generated migrations and what Diesel has:

  • Cot ORM doesn't require you to write a separate schema definition; it works purely on what your structs defined in the Rust code and previous migrations (hence it doesn't even need access to your database when generating new migrations)
  • Cot's migrations are fully database engine-independent; instead of writing SQL, it operates on a predefined set of possible operations (such as "create model", "add field to a model", etc.) that can optionally be augmented with raw SQL if needed. This also means there's no need to write "down" migrations separately, as the operations already have their undoing behavior defined in the ORM directly, rather than in the individual migrations.

Because of these two reasons, I think it was fair to say in the article that this level of convenience is nowhere to be seen in already existing ORMs.

Cot ORM doesn't require you to write a separate schema definition; it works purely on what your structs defined in the Rust code and previous migrations (hence it doesn't even need access to your database when generating new migrations)

This sounds nice for new applications, but I highly doubt that you can model everything that is commonly used in database migrations, as that is usually more involved that just adding/removing fields. For example how would you handle renaming a field? If you only know the before and the after state you cannot decide if it's really a rename or if it's a drop + add column operation. To be clear here: Diesel (and any other system) does also not have a solution to this, because it's fundamentally undecidable given the provided information. Also all this only discusses migrating your schema. For applications that exist for more than a week you usually also need to migrate data, which again is something that can hardly be generated automatically as this often depends on domain specific knowledge.

The other problem by having a single "model" struct is that it disallows a lot of common optimizations. For example you always need to fetch all fields from your database, even if you only need a single field. This become a larger problem as soon as you want to join several table. So yes you can go with that "single model" approach but it's not something that's clearly better, it's just something that

Cot's migrations are fully database engine-independent; instead of writing SQL, it operates on a predefined set of possible operations (such as "create model", "add field to a model", etc.) that can optionally be augmented with raw SQL if needed. This also means there's no need to write "down" migrations separately, as the operations already have their undoing behavior defined in the ORM directly, rather than in the individual migrations.

It's at least questionable if that's not the case for diesel as well. Sure the actual generated migration is using backend specific SQL, but that's also the case for any other solution as that's what you need to do in the end: Generate SQL that you can execute. For diesel this step is explicit, but none stops you to generate such SQL for different backends, which gives you as a user a greater amount of control. For Cot it seems to be impossible to control this step, which might become problematic for more complex migration cases.

Because of these two reasons, I think it was fair to say in the article that this level of convenience is nowhere to be seen in already existing ORMs.

As written before: I disagree about that. It's at least an extreme simplification to write it that way, as that statement is only true for certain very specific (and for non-trivial applications uncommon) boundary conditions. Also its not that the features do not exist in diesel, they are just differently due to different optimization targets.

1 Like

Yes, there are similar problems when renaming tables, too. This is something that I haven't implemented yet, but I have pretty concrete ideas on solving this, inspired by how Django handles this. The idea is to just implement heuristics – for renaming tables, for instance, we could check if all (or, say, 90% of) the fields between a removed and created model are the same; for renaming single fields handling a simple case where you only rename a field and keep all the other metadata (such as the field type) intact is easy to handle. When the migration generator sees something like this, it can ask the developer if the heuristic was correct, and generate either a rename or remove/create operation depending on the answer.

Is this going to be working 100% the time? Obviously not. Will it be good enough for 90%+ cases? I believe so.

Correct – I thought about this as well. The Cot migrations are Rust source files that just happen to have a very specific structure. The idea is to provide a "custom operation" that would be able to run whatever Rust code you like in a migration. The migration files that are generated keep the old versions of the models, so you can even use the ORM directly on these without worrying that you are working on the wrong version of the model. This, again, is inspired by how Django handles this.

The only missing part currently is that the Cot ORM doesn't yet provide this "custom code" operation, but with the current design, it's almost trivial to do and will certainly appear by the v0.2 release.

Yes and no. Again, I'm sort of talking about the (very near) future plans, but supporting running arbitrary SQL code in the migrations is going to be implemented (again – technically this is easy to do since it's all just Rust code). The difference between the Cot ORM and Diesel is that I'm trying to make the "common cases" easy, but also don't want to limit you if you need more power.

Again there is nothing stopping you from doing exactly that with diesel. It's just that years of usage have demonstrated that defaulting to SQL based migrations is more flexible for the common use-case, but you can also write your migrations with diesel in pure rust and it will just work as well.

At which point you would likely not have a database agnostic migration anymore.

That's exactly what I'm questioning. The common case is already simple with diesel. It's literally just: diesel migration generate --diff-schema and it works and gives you all that what you describe above. I cannot see what would be more easy by substituting that with cot migration generate instead. In both cases the user needs to write the rust code and in both cases something is generated from that that migrates the database schema from the old state in the new state. That might sound harsh but for me the important difference there is that diesel already supports all of that since quite a while, which means it's well tested and established. Cot currently only provides a fraction of that functionality and is explicitly untested. Sure that's a great learning project, but if you want something that works: Why not spend that time on improving an existing solution rather inventing your own incomplete thing?

1 Like