Dualo
Backend Architectures Deep Dive

ORM & data layer patterns

Active Record, Data Mapper, Query Builder, raw SQL. The data access pattern your framework ships shapes how you think about persistence.

1 min read

Your data layer translates between objects/structs in code and rows in a database. Four main patterns exist — each framework ships one, and the choice shapes how you model your domain.

Active Record (Rails, Django ORM, Eloquent): the model class IS the row. User.find(42).save() — methods on the class talk to the DB. Great for CRUD velocity, but models get fat fast, and testing usually means spinning up a DB.

Data Mapper (SQLAlchemy classical mode, Hibernate/JPA, TypeORM): models are plain objects; a separate 'session' or 'repository' handles persistence. More boilerplate, better separation of domain logic from storage, easier unit testing.

Query Builder (Knex, Kysely, sqlx, jOOQ, SQLAlchemy Core): you write queries in a fluent SQL-like DSL. Results deserialize to structs. More explicit than an ORM, more ergonomic than raw strings, usually typed.

Raw SQL (node-postgres, pq, psycopg2 without ORM): hand-written queries with parameterized placeholders. Best performance + explicitness; worst CRUD velocity. Used deliberately for hot paths or when the ORM abstraction is getting in the way.

Grounded on https://martinfowler.com/eaaCatalog/activeRecord.html

Next up

Server-rendered vs API + SPA vs server components

Three fundamentally different ways to build a web UI — each with its own backend implications for data, routing, and deployment.