The moment I first started learning backend development, I already knew that real applications involved much more than CRUD operations. What I didn't fully understand was how these things are actually built behind the scenes.
While building backend projects and reviewing a larger FastAPI codebase, I came across several concepts that repeatedly appear in real-world systems. The interesting part wasn't discovering these concepts—it was understanding why they exist and what problems they solve.
Here are 7 backend concepts that changed how I think about backend engineering.
1. Authentication (JWT)
The first question every backend needs to answer is:
"Who is making this request?"
Without authentication, anyone can access your endpoints and perform actions they shouldn't be able to. While building projects, authentication was one of the first concepts that made my backend feel more like a real application. Instead of allowing every request, users now had to log in, receive a token, and include that token when accessing protected routes.
This is where JWT (JSON Web Tokens) came in.
Implementing login, protected routes, and a /me endpoint helped me understand how applications keep track of users and verify their identity.
2. Authorization (RBAC)
Authentication tells us who the user is and "Authorization" tells us what they are allowed to do.
For example:
- A normal user can upload documents.
- An admin can verify documents.
- Another role might manage users.
As applications grow, managing permissions becomes increasingly important. Initially, it may seem easy to add a few checks wherever needed, but that approach quickly becomes difficult to maintain.
This is where RBAC (Role-Based Access Control) comes in.
Instead of scattering permission checks throughout the codebase, roles can be centralized and enforced through reusable dependencies and helper functions.
Learning RBAC helped me understand how larger applications manage permissions cleanly, while keeping the code organized and easier to maintain.
3. Dependency Injection
This was probably the concept that confused me the most when I started learning FastAPI.
I kept seeing things like:
db: Session = Depends(get_db)
and
current_user: CurrentUser
without fully understanding what was happening behind the scenes.
Eventually, I realized that dependency injection is simply a way of providing things like database sessions, authenticated users, and permissions to endpoints automatically.
Once it clicked, a lot of FastAPI's design started making sense. It also helped me understand how the same database session, current user, or permission checks could be reused across multiple endpoints without repeating the same code everywhere.
4. Global Exception Handling
In smaller projects, it's common to see:
raise HTTPException(...)
everywhere.
That works at first, but as the application grows, error handling can quickly become repetitive and harder to maintain.
While building projects, I learned how custom exceptions and global exception handlers can make this much cleaner.
Instead of repeating the same response logic in every route, the backend can define reusable exceptions such as:
- UnauthorizedError
- ForbiddenError
- ConflictError
- NotFoundError
The framework then handles these exceptions consistently across the entire application.
Besides reducing repetition, this approach also makes the code easier to read and helps keep API responses standardized.
5. Audit Logs
This was one concept I hadn't paid much attention to when I started learning backend development.
Imagine an admin verifies a document.
A few days later someone asks:
- Who verified it?
- When was it verified?
- What action was taken?
Without audit logs, there is no answer.
Audit logs create a history of important actions inside the system. They don't directly add new features for users, but they make systems much more reliable and traceable.
After implementing and reviewing them in real projects, I started noticing how important they are in applications where accountability and record-keeping matter.
6. Database Migrations (Alembic)
One thing I quickly realized while working with databases is that changing a model is easy, but safely updating an existing database is not.
What happens when a new column is added? Or a table structure changes after the application is already being used?
This is where Alembic comes in. A simple way to think about it is: Alembic is like Git for your database schema. It keeps track of changes and allows those changes to be applied in a controlled and repeatable way.
Instead of manually modifying the database, migrations provide a structured and controlled way to evolve the schema over time.
Learning Alembic helped me understand that managing a database isn't just about creating tables. It's also about safely handling change as an application grows and new requirements emerge.
7. Asynchronous Programming
One of the most intimidating concepts I encountered was Async. At first, I understood the syntax, but I didn't fully understand when asynchronous programming was useful or what problem it was trying to solve.
What I learned is that asynchronous programming allows a server to handle other work while waiting for certain operations to complete.
This becomes especially useful for:
- Database operations
- Network requests
- File handling
- External APIs
One lesson that really stuck with me was that simply writing:
async def my_function():
await some_async_operation()
does not automatically make a function asynchronous. The code inside the function also needs to perform asynchronous operations for async to provide any real benefit. Understanding that distinction helped me appreciate why async exists and where it actually provides value in backend applications.
Final Thoughts
Before learning these concepts, I mostly focused on getting features to work. If an endpoint returned the correct response, I was happy.
But while building project and reviewing a larger backend, I started realizing that backend development is about much more than making things work.
Questions like:
- Who is making this request?
- Are they allowed to do this?
- What happens if something goes wrong?
- How do we track important actions?
- How do we safely change the database later?
started becoming just as important as the endpoints themselves.
I'm still learning, and I have a lot more to explore. But understanding concepts like JWT authentication, RBAC, dependency injection, audit logs, Alembic, and async programming has given me a much better picture of how real backend applications are built.
For me, these concepts were the first step from simply building APIs to thinking more like a backend engineer.
Top comments (0)