Thanks for the response Roman ! I like your suggestion about making the docs more directly call out Joist's strengths... I'll work on that. create table first ... codegen generates model files Yep! Just like RoR auto-adds first_name getters/setters, Joist does the same for firstName /etc. columns (as well as has many/has ones); the difference is that Joist adds it to a base class, so that TypeScript can see the fields & do type-checking. But net-net is that you get the same "just an empty model" "class Author {}" when starting out (...okay with an "extends AuthorCodegen" snuck in there :-)) as you do with Rails. I just really loved that low-boilerplate aspect of Rails. reactive ... in Rails Yep, it's similar, although AFAIU Rails the rules are only reactive on fields within the current model; in Joist, you can have rules that run when fields on related entities change, i.e. re-run an Author rule whenever it gets a new Book, or one of its Books' titles changes. First example is not N+1 safe Ah yeah, I think I focused too much on explaining what N+1 was for new comers. That's fair, that page could do a tldr first. Running tests in transactions ... then roll them back Ah yeah! That is also what Rails does, afaiu (by default? I think it's configurable iirc), but that means you can't test any code that commits transactions (or maybe you could use subtransactions?). Which for us is GraphQL mutation resolvers. Afaict the flush database approach is ballpark-same-speed and you don't have that same limitation. Another upshot of Joist's approach is that if your test fails, any test data you've setup will still be in your local db to look at/diagnosis if you need to. But I think both are good; as long as the tests are fast! :-) book.reviews Yep! That makes sense, but the wrinkle is: is book.reviews a BookReview[] or Promise<BookReview[]> ? Other ORMs like TypeORM afaiu make you choose one or the other up-front, when writing the model. If you choose Promise<BookReview[]>, it is the safest, but annoying to always .load() it to access. If you choose BookReview[], it is really easy to access, but then you risk accidentally calling it w/o it being loaded yet and getting a runtime error. I agree Joist's approach looks odd, but it's both: a) really safe; if book.reviews has not be explicitly preloaded with a populate hint, you must call reviews.load() ; but it's also b) really ergonomic b/c if you have preloaded book.reviews , then some TypeScript magic automatically makes .get visible to you, and you get really pleasant synchronous access. In a way, this is similar to Prisma, being able to use "kinda like GQL" snippets of a load hint ({ book: { author: { publisher } }) to preload a bunch of data in 1 await call, but with Joist the hints are based on the combination of your db schema + your custom relations & fields (kinda similar to ActiveRecord scopes) instead of strictly being database tables like they are in Prisma. Author.find / instead of em.find Yeah, honestly 80% of this is that we started with MikroORM and its EntityManager API, before writing Joist & migrating our codebase to it. But the other 20% is that, unlike Rails (but the same as Mikro and older ORMs like Hibernate), Joist uses a unit of work pattern to delay/batch SQL statements until a "flush" method is called. So we need some sort of "EntityManager" / "Unit Of Work" "thing" to pass around, so :shrug: I think it's net/net not a big difference. You're right that it is not strictly the Active Record pattern, but I think Joist follows the spirit of the Active Record pattern, which is that models extremely closely/strictly match the database tables, vs. the Data Mapper pattern, which is where the models can drift from the schema in ways that seem nice at first but quickly get complex imo.