Skip to content
Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.
DatabaseOrmCC-BY-4.0

ORM Conventions

Use an ORM (Prisma or equivalent) without its convenience becoming a performance or security liability: N+1 detection and eager loading, transactions for multi-write invariants, parameterized queries only, selecting only needed columns, ORM-managed migrations, and when to drop to raw SQL.

Mby @markdownersPublished August 21, 2026 · ~3 min read

0 downloads · Used by 0 stacks

An ORM trades explicit SQL for convenience, but every query it generates still runs against a real database with real cost — know what query your ORM call actually produces, especially for anything inside a loop or returning a list.

N+1 detection

  • Treat any loop that triggers a query per iteration (fetching a list, then accessing a related field per item that lazily loads) as a bug, not a style choice — this is the single most common ORM performance problem and it's invisible in code review unless you specifically look for it.
  • Eager-load relations up front (include/with/.populate() equivalent) whenever the related data will be accessed for every item in a list, instead of relying on lazy per-item loading.
  • Turn on query logging in development and actually read it during code review of any new list-rendering code path — the fastest way to catch an N+1 is seeing "20 nearly identical queries" in the log, not waiting for a production slowdown.

Transactions

  • Wrap multi-write operations that must succeed or fail together (creating an order and decrementing inventory) in a database transaction through the ORM's transaction API, never as separate sequential calls — a partial failure between two unwrapped writes leaves the database in an inconsistent state with no automatic recovery.
  • Keep transactions short and avoid external I/O (API calls, sending email) inside them — a long-held transaction holds locks and blocks other writes for its entire duration.

No raw string interpolation

  • Never build a query by interpolating user input into a raw SQL string, even through the ORM's raw-query escape hatch — this reintroduces SQL injection that the ORM's parameterized query builder exists specifically to prevent.
  • When raw SQL is genuinely needed, use the ORM's parameterized raw-query API (placeholders bound separately from the query string), never template-literal concatenation of values into SQL text.

Selecting only needed columns

  • Select only the columns actually used by the calling code (select: { id, name }) rather than fetching entire rows by default, especially for large tables or large text/blob columns — fetching unused columns wastes bandwidth and memory on every single call site, and it compounds across a list.
  • Be deliberate about relation payload size the same way — a select/include that pulls a full related object when only one field of it is used is the same waste one level deeper.

Migrations

  • Make every schema change through the ORM's migration tool, generating a migration file that's committed to version control — never make an ad hoc schema change directly against the database that the ORM's model definitions don't know about, since it desyncs the model layer from actual schema and the next generated migration will try to "fix" a change that was already applied by hand.
  • Review generated migration SQL before applying it in production, especially for anything the ORM infers ambiguously (renaming a column can generate a drop-and-recreate instead of a rename, silently losing data) — never trust an autogenerated migration blindly on a table with real data.

When to drop to SQL

  • Drop to raw SQL (through the ORM's parameterized raw-query API) for queries the ORM's query builder genuinely can't express efficiently — complex aggregations, window functions, or multi-table joins that the builder would otherwise turn into multiple round trips or an inefficient generated query.
  • Keep raw SQL isolated and well-commented with why the ORM abstraction wasn't sufficient, so a future reader doesn't "helpfully" rewrite it back into ORM calls and reintroduce the original performance problem.
Badge

Link back to this module from your own README.

Get it on Markdowners
[![Get it on Markdowners](https://markdowners.com/mdstack-badge.svg)](https://markdowners.com/m/markdowners/orm-conventions)

Comments (0)

Sign in to comment. Sign in

No comments yet. Be the first to add one.

Discussions about this module

No discussions about this module yet.

Start a discussion