Until Phase 3, everything lived inside the browser.
The frontend was clean.
The architecture was scalable.
The product felt complete.
But technically?
It was still self-contained.
No server.
No APIs.
No system boundaries.
No production-level backend thinking.
Phase 4 changed that.
This is where the project stopped being a polished frontend
and started becoming a real full-stack system.
Day 10 – Laying the Backend Foundation the Right Way
Most developers, when adding a backend to a portfolio, do this:
- Install Flask
- Create
app.py - Add some routes
- Call it done
I didn’t want that.
Because this backend wasn’t just about serving JSON.
It was about building something that looks and behaves like a production service.
1. Starting with Structure, Not Routes
The first thing I did was create the backend using uv and properly structure the environment.
Not just a folder.
A real backend project.
That meant:
- Virtual environment management
- Proper dependency installation
- Structured directory layout
Before writing a single route.
Because architecture begins before code.
2. Environment Configuration – Thinking Beyond Localhost
Instead of a single .env file, I created:
.env.production.env.development.env.testing
Why?
Because real systems don’t run in one mode.
They run in:
- Local development
- CI/testing
- Production deployment
And each environment:
- Has different secrets
- Different debug levels
- Different logging behaviour
- Different security rules
This decision alone separates:
hobby backend
from
deployable backend
3. Configuration as Code (settings.py)
I created app/config/settings.py and defined configuration classes.
Not just variables.
Classes.
Why?
Flask supports environment-based configuration through object-based loading.
So instead of:
app.config['DEBUG'] = True
I built structured config classes like:
- DevelopmentConfig
- ProductionConfig
- TestingConfig
This gives:
- Clear separation of concerns
- Centralised configuration
- Easy scalability
- Cleaner environment switching
This is how backend systems scale safely.
4. The Application Factory Pattern
Instead of writing:
app = Flask(__name__)
I implemented:
def create_app():
...
This is called the Application Factory Pattern.
And it’s critical for:
- Testing
- Extension initialisation
- Clean separation of app creation
- Avoiding circular imports
- Multi-instance setups
This is how larger Flask systems are built.
Not in tutorials — but in real services.
5. Extensions Layer
I created:
app/extensions.py
And registered all extensions there.
Why isolate extensions?
Because:
- It prevents circular dependencies
- Keeps
__init__.pyclean - Makes the system modular
- Allows independent testing
Again — small decision, big scalability impact.
6. Blueprint-Based Routing
Instead of dumping routes in one file, I designed:
route_blueprints
This allows:
- Separation by domain (projects, contact, health, etc.)
- Modular expansion
- Cleaner code ownership
- API versioning later if needed
You don’t build blueprints for 3 routes.
You build blueprints when you expect growth.
And I expect growth.
Day 11 – Turning Architecture into Real APIs
Day 10 built the system.
Day 11 made it useful.
1. Creating Real Routes
I implemented routes for:
- Projects
- Skills
- Achievements
- Health
- Contact
Now the frontend no longer depends on local JSON.
The backend now exposes structured APIs.
Which means:
The architecture decision from Day 7 (JSON-first design)
paid off.
Because now:
JSON → API
Same shape
Zero UI rewrite
That’s intentional design maturity.
2. Moving Frontend Data into Backend
I migrated all frontend data into:
api/data/
This was a psychological shift.
Now the backend is the source of truth.
The frontend becomes a consumer.
That’s real system ownership.
3. Introducing Pydantic – Validation Done Properly
Most Flask tutorials validate data manually.
I introduced Pydantic models.
Why?
Because:
- Strong validation
- Type enforcement
- Cleaner error messages
- Self-documenting schemas
Now the Contact route, for example:
- Doesn’t just accept any JSON
- It validates the structure
- It enforces required fields
- It ensures data integrity
That’s how production APIs behave.
4. Logging and Error Handling
Instead of printing errors or returning raw messages:
I implemented:
- Structured logging
- Centralised error handling
- Clean response formats
This is important for:
- Debugging production
- Monitoring
- Observability
- Reliability
It also signals something important:
This backend is meant to run in the real world.
5. Finalising the Contact Route
The Contact route is now:
- Validated via Pydantic
- Logged
- Error-handled
- Structured in blueprint
- Ready for future email service integration
It’s no longer:
a form endpoint
It’s:
a system boundary.
Why Day 10 & 11 Matter
Most developers build the backend as an afterthought.
I treated it as:
- an architecture layer,
- a deployment-ready service,
- a long-term foundation.
By Day 11, the system had:
- Config abstraction
- Environment separation
- App factory pattern
- Modular blueprints
- Data validation
- Structured logging
- Proper error handling
That’s not a portfolio backend.
That’s real backend engineering.
Day 12 – Observability, Discipline, and Production Thinking
If Day 10 was architecture
and Day 11 was functionality,
Then Day 12 was about something most developers ignore:
Observability.
Because building APIs is easy.
Running APIs reliably is not.
1. Extracting Logging into Its Own Layer
I created:
app/utils/logger.pyapp/utils/__init__.pyapp/utils/error_handler.py
Why isolate logging?
Because logging is not a feature.
It’s infrastructure.
By extracting it:
- Routes remain clean.
- Business logic is not polluted.
- Logging behaviour can evolve independently.
- Production formatting can differ from development formatting.
This is what happens in mature systems:
Logging becomes a service layer.
2. Centralised Error Handling
Instead of handling errors inside each route:
try:
...
except Exception as e:
...
I introduced centralised error handling.
Why?
Because error behaviour should be consistent across the entire API.
Now:
- All exceptions follow structured responses.
- All failures are logged properly.
- The API has predictable failure formats.
That predictability matters when:
- Frontend integrates deeply
- Monitoring tools are added
- Alerts are configured
- Logs are parsed automatically
This is the difference between:
“It works.”
and
“It’s production-ready.”
3. Adding Logging to Every Route
On Day 12, I went back and integrated logging into:
- Contact route
- Contact validation logic
- Projects route
- All remaining endpoints
Not because it was required.
But because consistency matters.
Every request now:
- Leaves a trace.
- Records meaningful information.
- Fails loudly (in logs), not silently.
This is the kind of invisible work that makes systems maintainable long-term.
And this is exactly the kind of discipline most portfolio backends skip.
Day 13 – Closure, Review, and Merge
Day 13 was not about new code.
It was about maturity.
1. Final Route Review
Before merging, I reviewed:
- Route structure
- Blueprint registrations
- Logging consistency
- Validation integrity
- Response formats
- Naming conventions
This is what tech leads do before release.
Not because something is broken.
But because once merged, it becomes baseline architecture.
2. Pushing, Merging, and Closing the Branch
This might sound trivial.
But merging the backend branch symbolised something important:
The system is no longer frontend-only.
It is now officially full-stack.
The merge wasn’t just code integration.
It was an architectural consolidation.
What Phase 4 Actually Achieved
By the end of Day 13, the project now has:
1. Structured Backend Architecture
- Application Factory Pattern
- Environment-based configuration
- Blueprint modular routing
- Extensions isolation
2. Validation Layer
- Pydantic schemas
- Structured request validation
- Clean error responses
3. Observability
- Centralised logger
- Structured logs
- Error handler layer
- Route-level logging integration
4. Clean Separation of Concerns
Frontend:
- UI rendering
- Data consumption
Backend:
- Data ownership
- Validation
- API contracts
- System boundary
That separation is huge.
Because now the project is not a React app with some Python attached.
It’s:
A client-server system with clear responsibilities.
The Bigger Shift: From Developer to System Thinker
Phase 3 made me think like a frontend architect.
Phase 4 made me think like a systems engineer.
Because now I had to consider:
- Configuration management
- Environment isolation
- Validation enforcement
- Error standardisation
- Logging discipline
- Modularity
- Future scaling
This is no longer “I built a portfolio”.
This is:
I designed and implemented a maintainable, extensible, observable full-stack system.
And that difference shows in code.
The Real Outcome of Day 10–13
This backend is not overengineered.
It’s intentionally structured.
If tomorrow I decide to:
- Add authentication
- Add database persistence
- Add rate limiting
- Deploy to production
- Add monitoring tools
- Integrate CI/CD
The foundation is already ready.
That’s what good architecture does.
It prepares you for change before change arrives.
Closing Phase 4
By the end of Day 13:
The project is no longer:
- A static portfolio
- A frontend showcase
- A React experiment
It is now:
- Structured
- Layered
- Observable
- Deployable
- Extendable
It behaves like a real product.
And that was always the goal.
If you’re following along, the complete source lives here:
👉 GitHub Repository: Portfolio.

Top comments (0)