Backend Features That Make a Junior Python Portfolio Stand Out in 2026

Problem
A working API is the floor, not the ceiling. I built my first FastAPI + SQLAlchemy + PostgreSQL project, ran it, saw a 200 OK, and called it done. Then I showed it to a senior engineer friend, and they asked five questions in a row: “Where are the migrations? Where is the auth? Where are the tests? Where is Docker? Where is the CI?”
The project “worked,” but it did not signal that I understood backend development. That is the gap most junior portfolios fall into.
Purpose
This post shows which backend features make a junior Python portfolio stand out in 2026. The key point is a 12-feature checklist grouped into three tiers, with the must-haves in bold. If you ship all of them, you are in the top decile of junior Python portfolios.
Environment
- Python 3.11
- FastAPI (latest)
- PostgreSQL 15
- SQLAlchemy 2.0
- Alembic for migrations
- pytest + httpx for tests
- Docker + docker compose
- GitHub Actions for CI
- structlog or loguru for structured logging
- passlib[bcrypt] or argon2 for password hashing
The Direct Answer
The features that make a junior Python backend portfolio stand out are a small, well-known set, and most candidates miss half of them. The high-signal features are:
- Real auth with hashed passwords and role-based access control (FastAPI
Depends,passlib[bcrypt], JWT or session). - Alembic migrations, not
Base.metadata.create_all(). - Tests that exercise real business rules with
pytest+httpx.AsyncClientagainst a real Postgres (Testcontainers). - A
repository / service / routersplit, with business logic out of the route handlers. - Transactional SQL for anything that mutates more than one row (use
with_for_update()for inventory, idempotency keys for payments, unique constraints to prevent double-booking). - A
Dockerfileanddocker-compose.ymlthat bring up app + database with one command. - A CI pipeline (GitHub Actions) that runs lint + tests on every push.
- Structured logs and a
/healthendpoint. - One non-trivial background concern that is not just CRUD: a scheduled job, a webhook receiver, or a queue worker.
If your repo has all nine, you are in the top decile of junior Python portfolios. If you only have CRUD endpoints, you are in the median.
Tier 1 - Non-Negotiable
The must-haves. Ship all of these before you ship anything else.
Real auth (top signal)

Recruiters look for this first. If your auth is “trust the X-User-Id header,” the rest of the project does not matter. Real auth means:
- hashed passwords (bcrypt or argon2 via
passlib) - JWT or server-side sessions
- role-based access on protected routes
- tests for unauthenticated and unauthorised access
Layered architecture: router -> service -> repository
Business rules in the service, never in the route. This is the most common junior mistake and the easiest one to spot.
Alembic migrations from commit 1
No create_all. Reviewers will check the migrations folder.
Real tests
A few tests that hit your endpoints with httpx.AsyncClient and assert on business outcomes, not on status_code == 200. Run them against a real Postgres (Testcontainers is the easiest way).
A docker-compose.yml
Brings up app + Postgres with one command. The Dockerfile should be multi-stage, not 800MB.
Tier 2 - High Signal
Do these too. They are not as obvious as Tier 1, but they push you above the median.
Transactional SQL
select ... with_for_update() for inventory, idempotency keys for money operations, unique constraints to prevent double-booking. This is the next visual, the side-by-side that separates “ORM magic” from “explicit SQL”.
CI pipeline
GitHub Actions is fine. Run ruff, mypy on the touched paths, and pytest on every push. A green badge in the README is a small thing that signals a lot.
Structured logging
Use structlog or loguru, JSON output, request IDs. Reviewers can grep one request through the whole flow.
/health and /ready endpoints
So the project looks operable. /health is liveness, /ready is “the database is reachable and migrations are applied.”
Tier 3 - Bonus Polish
These are the things that turn a strong portfolio into a memorable one. Pick two or three, do not try to do all of them.
- One background job (Celery, Arq, or just a thread +
apscheduler) for things like email, billing, or daily aggregation. - A webhook receiver with signature verification.
- Rate limiting on auth endpoints (
slowapi). - OpenAPI customisation, contact details, server list, and a polished
README.mdwith a curl example.
ORM Joins vs. Simple Core Queries

The single most common mistake I see in junior repos is putting every join, every aggregate, and every filter through the ORM. It works, but the SQL is invisible and the queries get long. The recommended path is to put explicit Core queries in the repository layer and let the service call them. The visual above is the exact side-by-side I want in your head while writing SQL.
A Route Handler That Shows the Layered Split
Here is the kind of route that signals “this candidate gets it”. Notice what the route does not do: it does not validate business rules, it does not touch the DB, it does not format logs.
from fastapi import APIRouter, Depends, statusfrom sqlalchemy.ext.asyncio import AsyncSessionfrom app.deps import get_session, current_userfrom app.schemas import OrderIn, OrderOutfrom app.services.orders import create_order
router = APIRouter(prefix="/orders", tags=["orders"])
@router.post("", response_model=OrderOut, status_code=status.HTTP_201_CREATED)async def post_order( payload: OrderIn, session: AsyncSession = Depends(get_session), user=Depends(current_user),): return await create_order(session, user_id=user.id, payload=payload)That is the signal. The business rule lives in the service.
A Service With a Real Business Rule
from uuid import UUIDfrom sqlalchemy.ext.asyncio import AsyncSessionfrom app.repositories.orders import OrderRepositoryfrom app.repositories.inventory import InventoryRepositoryfrom app.errors import OutOfStock
async def create_order(session: AsyncSession, user_id: UUID, payload): inventory = InventoryRepository(session) orders = OrderRepository(session)
for item in payload.items: if not await inventory.has_stock(item.sku, item.quantity): raise OutOfStock(item.sku)
order = await orders.create(user_id=user_id, items=payload.items) await inventory.decrement([(i.sku, i.quantity) for i in payload.items]) return orderThis is the kind of code that gets the offer, not the route handler. The route parses, the service decides, the repository persists.
Why This Matters
Engineers reviewing junior portfolios are scanning for evidence of a specific mental model: “this person understands that an HTTP handler is not the application.” If your route handler does validation, talks to the DB, formats the response, and writes the log, you have not demonstrated that mental model.
The nine high-signal features above are basically a vocabulary test. If your repo contains them, you are speaking the language the interviewer is listening for. If it does not, you are not.
Common Mistakes
- Putting everything in the route handler. The single most common junior mistake, and the easiest one to spot.
create_allinstead of migrations. Signals that you have not read the FastAPI / SQLAlchemy docs past the first tutorial.- Auth that is “username is a header.” Real auth is hashed passwords, real tokens, real role checks, and tests for unauthenticated access.
- Tests that just assert
200. Reviewers can tell. - No
docker-compose.yml. This is a 30-minute cost that flips the “deployable?” question to yes. - No CI. A green badge in the README is a small thing that signals a lot.
Summary
In this post, I showed the 12 backend features that make a junior Python portfolio stand out in 2026. The key point is the three-tier checklist: ship the five Tier 1 items first (real auth, layered architecture, migrations, real tests, Docker), add the four Tier 2 items (transactional SQL, CI, structured logging, health endpoints), then pick two or three Tier 3 polish items. Build all of them and you are in the top decile of junior Python portfolios.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 r/Python: What backend projects would actually aid me stand out for a junior Python backend developer role?
- 👨💻 FastAPI Security and authentication
- 👨💻 SQLAlchemy 2.0 select() with FOR UPDATE
- 👨💻 Docker compose specification
- 👨💻 GitHub Actions for Python
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments