Everyone Was New to ORMs, So I Wrote a Backend Development Guide
A 17-section development guide written to stop copy-paste duplication
Every Company Codebase Looked the Same
A while back I got to dig through two or three company projects. The thing was, all three looked identical. Code written in one place got copied into the next project, and from there copied again. Whole sets of code traveled around like that. As a result, even within a single project the same logic was scattered all over, and the same query was rewritten file after file. Duplication, duplication, and more duplication.
It runs fine, sure. The problem comes when you have to fix something. You have to change the same thing in several places at once, and if you miss even one, that’s where the bug leaks out.
Around that time I started a backend study group. We’d come together because someone had a solid idea for a trading app, but most of the members were touching ORMs and domain-driven design for the first time. If I left everyone to wing it, the same copy-paste code I’d seen at companies was bound to show up again. So I decided to write a guide myself. Partly to tell the members “this is how we build things in this project,” and partly to head off the duplication before it could pile up.
Using a real project, a trading MVP, as the foundation, a 17-section backend development guide came out of it, and this series generalizes that. The domain is stock accounts and portfolios, but the patterns themselves apply to any backend.
Honestly, I hesitated about whether to publish this at all. I wrote it for the study members, not for an outside audience. But it’s a document I put real effort into, and it seemed like it could be useful to someone who’s just getting into backend work and keeps wondering “where on earth do I put this code.” So I’ve stripped out the project-specific color, generalized it, and I’m posting one summarized piece at a time. Read it less as “the right answer” and more as “here’s how we organized things.”
Picking the Stack First
To write the guide, we first had to agree on what to build it with. What we landed on was Python 3.11 with FastAPI, and for the data layer, SQLModel layered on top of SQLAlchemy.
Putting Python at the base was largely about type hints. Once you annotate types, FastAPI and Pydantic take them as-is and validate against them, so you catch mismatched data during development before it bites you. There were two reasons for picking FastAPI as well. One is dependency injection. Instead of constructing things like a DB session or auth inside every function, you have them injected, which makes testing a lot easier. The other is async. You can handle other requests while waiting on I/O, which suits a service that calls external APIs often.
Layering SQLModel onto the data layer was the crux of it for a study group. If SQLAlchemy is the ORM standard, SQLModel sits on top and merges the DB table model and the API data model into a single class. When you manage models in two separate copies, you get an endless stream of bugs from the two drifting apart, and this wipes out that whole class of bug. For people new to ORMs, it’s one fewer layer to explain, which made it easier to write the guide too.
Validation is handled by Pydantic, background work by Celery, testing by Pytest, and the environment by Docker Compose. Only after laying down this combination did we start writing up “so, how do we actually lay out the code” in earnest.
Map of the Series
The guide is long. Unloading it all at once would be too much to digest, so let me lay out the whole picture first. To say it up front: nearly every part ends up pointing at one thing. Where do you cut off duplication? Logic goes into the model, queries into the repository, and shared code into base classes. We’ll cover one part at a time, in the order below.
- (this post) Everyone Was New to ORMs, So I Wrote a Backend Development Guide: why it got written and which stack we chose
- The Folder Layout Is the Design: laying out project structure on a domain basis
- Routers for HTTP Only, Logic in Services: where to cut the layers
- Put Logic in the Model and Duplication Disappears: working with ORM domain models
- Data Access Through the Repository Only: a generic BaseRepository to keep queries in one place
- Validate at Every Boundary: schemas (DTOs) and layered defense
- Catch Only the Exceptions You Can Handle: an exception-handling strategy
- So You Never Write the Same Code Twice: shared modules, base classes, and naming conventions
In the next part, we start with the folder structure. Where you put your code is half the design, so that’s the right place to begin.