Learn how to build, test, deploy, and maintain robust software systems.
A backend application needs to store and retrieve data, and most of the time that data lives in a relational database. In Computer Science you learned SQL, normalization, and ACID. This lesson focuses on the application side: how a backend talks to a database, what an Object-Relational Mapper (ORM) is and why it is useful, how migrations version your schema, and how to avoid common performance traps like the N+1 query problem. The examples use Prisma and SQLAlchemy, two popular ORMs, but the ideas — models, relationships, migrations, connection pooling, and transactions — apply to any ORM or query builder.
Databases provide durable storage so data survives restarts and can be queried efficiently.
An Object-Relational Mapper maps between application objects (classes/records) and database tables, translating your code into SQL.
| Approach | What it is | When useful |
|---|---|---|
| ORM | Maps objects to tables | Typical CRUD and relationships |
| Query builder | Builds SQL programmatically | When you want control without raw strings |
| Raw SQL | Write SQL directly | Complex or highly-tuned queries |
A model represents a table. Relationships (one-to-one, one-to-many, many-to-many) are expressed as references between models.
Opening a database connection is expensive. A connection pool keeps a set of reusable connections so the application can borrow and return them quickly, which matters when many requests arrive at once.
A migration is a versioned, ordered change to the database schema. Migrations let you evolve the schema safely across environments instead of editing production by hand.
Seeding inserts initial or sample data, useful for development and tests.
If you load a list of users, then query each user’s posts one at a time, you make 1 query for the users plus N queries for their posts — N+1 queries. This is slow.
The fix is to fetch related data in one go with a join or eager loading.
When several writes must succeed or fail together, wrap them in a transaction. For example, creating an order, decreasing inventory, and recording a payment must be atomic.
The include statement eager-loads related posts, avoiding the N+1 problem.
One user has many posts.
(a foreign key on Post references User)
Eager loading fetches related data in fewer queries.
(eager loading is dramatically faster)