A reflection on two backend engineering internship projects that shaped my understanding of system design, API architecture, and working within existing codebases.
When people ask what I worked on during my internship, the easiest answer is:
"I built APIs, authentication systems, and backend services."
Technically, that is true.
But that answer misses the most important part.
The most valuable lessons I learned didn't come from writing code. They came from dealing with complexity, making architectural decisions, and solving problems that were much bigger than they first appeared.
Looking back, two tasks stand out more than any others.
The first was Insighta Labs+, a backend platform that took about 16 days of active engineering effort across three progressive stages and pushed me deeper into system design than anything I had worked on before.
The second was building Funnel Display APIs for SEIL, a feature that took about 3 days of focused implementation and taught me another important side of backend engineering: how to extend an existing system without breaking its foundation.
One taught me how to build systems.
The other taught me how to build within systems.
Both changed how I approach backend engineering.
Task 1: Insighta Labs+ — Building One Backend for Multiple Interfaces
Why I Picked This Project
If I had to choose the project that stretched me the most during the internship, it would be Insighta Labs+.
Not because it was simply a large project.
Not because it used unfamiliar technologies.
But because it forced me to think beyond individual endpoints.
Across three stages and about 16 active days of implementation, I wasn't just building an API.
I was building a backend platform that needed to support:
- A web application
- A command-line interface (CLI)
- Multiple user roles
- Authentication and authorization
- Search workflows
- Data ingestion pipelines
Each stage built on the previous one.
This meant early architectural decisions became foundations for everything that came after.
The breaks between stages also gave time to review what was already built, identify weaknesses, and approach the next stage with a clearer understanding of the system.
This was where I started seeing the difference between writing backend code and engineering backend systems.
What It Was
Insighta Labs+ is a profile intelligence platform built around a shared backend architecture.
The platform supports:
- GitHub OAuth authentication
- Profile intelligence workflows
- Role-based access control
- CSV profile uploads
- Search and filtering
- Data export
The interesting part was not any single feature.
The challenge was ensuring every interface depended on the same backend rules.
A browser user and a CLI user should receive the same:
- Authentication behavior
- Authorization decisions
- Data consistency
- API responses
The backend had to become the single source of truth.
The Problem It Was Solving
One common problem in software systems is allowing different clients to slowly become different products.
The web application starts implementing its own logic.
The CLI starts duplicating behavior.
Authentication rules become inconsistent.
Eventually, nobody can confidently explain how the system actually works.
The goal with Insighta Labs+ was to prevent that.
The backend needed to own:
- Business logic
- Authentication
- Authorization
- Search behavior
- Data access rules
Every client should consume the backend instead of recreating it.
How I Approached It
My approach was guided by one principle:
Every client should depend on the backend, not the other way around.
This affected every major decision.
Authentication Across Web and CLI
Supporting browser authentication was straightforward.
Supporting CLI authentication was more interesting.
The browser flow used traditional GitHub OAuth.
The CLI flow required PKCE (Proof Key for Code Exchange), allowing secure authentication without exposing client secrets.
The backend had to manage:
- OAuth state
- PKCE verification
- Authorization code exchange
- Callback validation
- Session creation
A simple login feature became a system of interacting parts.
Secure Token Handling
I avoided storing raw tokens directly.
The system used:
- Opaque access tokens
- Refresh token rotation
- Token hashing
- Session lifecycle management
The goal was to make authentication secure while keeping the developer experience predictable.
Role-Based Authorization
The platform supported different user roles:
- Analysts
- Administrators
Instead of scattering permission checks throughout the application, authorization was handled centrally.
This made the system easier to maintain and reduced the chance of inconsistent access rules.
Natural Language Search
One of the most interesting parts was building deterministic search.
Instead of using AI models, I built a parser that could understand queries like:
"young males from Nigeria"
and convert them into structured filters.
The parser handled:
- Gender detection
- Age ranges
- Country extraction
- Validation
The goal wasn't to understand every possible sentence.
The goal was to understand expected user intent reliably.
Performance Improvements
As the system grew, repeated queries became expensive.
To improve efficiency, I introduced:
- Query normalization
- Cache key standardization
- Response caching
- Cache invalidation after updates
The challenge was not only making things faster.
It was making them faster without serving outdated information.
What Broke
A lot.
And that was where the real learning happened.
OAuth Complexity
OAuth becomes significantly harder when supporting multiple clients.
The browser flow and CLI flow introduced different requirements.
Small mistakes in:
- Redirect URLs
- State validation
- Token exchange
could completely break authentication.
Search Edge Cases
Users rarely search exactly how developers expect.
Some queries created conflicting filters.
Others were ambiguous.
Instead of guessing, I made the parser fail clearly and predictably.
Cache Consistency
Caching solved performance issues.
But it introduced a new problem:
How do you know when cached data is no longer correct?
The solution was automatic cache invalidation after data mutations.
What I Took Away
Insighta Labs+ changed my mindset.
Before this project, I thought mainly in terms of features.
After it, I started thinking in terms of systems.
I learned:
- Authentication is a system, not just a login endpoint.
- Authorization is an architecture decision.
- Caching is a consistency problem.
- APIs become products when multiple clients depend on them.
Task 2: Funnel Display APIs — Building Features That Fit Existing Systems
Why I Picked This Project
Compared to Insighta Labs+, this task was smaller in scope and took about 3 days of focused implementation.
But it represented another important side of backend engineering.
In real-world teams, you rarely build everything from scratch.
Most of the time, you are extending an existing codebase.
The challenge is not just making your feature work.
The challenge is making it belong.
That is why I picked this project.
What It Was
The Funnel Display API was a backend feature built for SEIL to expose funnel and stage information required by the frontend.
The implementation introduced four authenticated endpoints:
- Retrieve available funnels
- Retrieve full funnel details
- Retrieve stages for a funnel
- Retrieve a specific stage
The work also included:
- Database migration
- Service logic
- Controller implementation
- Unit test updates
- Swagger documentation
The Problem It Was Solving
The frontend needed a clean way to display funnel information.
Instead of exposing database structures directly, the backend needed to provide meaningful product-level data.
The API needed to answer questions like:
"Show this user's funnel."
"Show the stages inside this funnel."
"Show details about this specific stage."
The backend became the bridge between stored data and the user experience.
How I Approached It
Before writing code, I studied the existing architecture.
SEIL already had:
- NestJS
- TypeScript
- PostgreSQL
- JWT authentication
- Repository-based data access
- Swagger documentation
The goal wasn't to introduce a new style.
The goal was to follow the existing one.
Database Migration
The first step was adding the required database structures.
Instead of manually modifying the database, I created a migration.
This made schema changes:
- Repeatable
- Version controlled
- Safe across environments
A database change is not just a database change.
It becomes part of the application lifecycle.
API Design
The endpoints were designed around frontend needs.
A frontend does not think:
"Return rows from the funnel table."
It thinks:
"Display this funnel and its progress."
That difference influenced the API structure.
The routes focused on:
- User ownership
- Authentication
- Meaningful responses
Service Layer Design
I kept responsibilities separated.
Controllers handled:
- Request handling
- Validation
- Responses
Services handled:
- Business logic
- Data retrieval
- Rules
This made the feature easier to test and maintain.
Testing
I updated unit tests to verify funnel service behavior.
I also manually tested the endpoints through Swagger:
- Start the development server
- Authenticate with a JWT
- Call each endpoint
- Verify responses and status codes
The result was a frontend-ready API surface.
What Broke
The biggest challenge wasn't a failed build.
It was ensuring the feature matched the existing system.
A backend feature is not successful just because it works.
It should feel like it was always part of the application.
That meant paying attention to:
- Existing patterns
- Response formats
- Error handling
- Authentication rules
What I Took Away
This task taught me that backend engineering is also about integration.
I learned:
- Migrations are part of feature development.
- API design should reflect user actions.
- Existing architecture deserves respect.
- Good features fit naturally into systems.
Final Thoughts
These two projects represented two different sides of backend engineering.
Insighta Labs+ taught me how to build systems that evolve across multiple stages, interfaces, and requirements.
Funnel Display APIs taught me how to contribute to an existing system and extend it safely.
One required creating foundations that could support future growth.
The other required understanding an existing foundation and building on top of it.
Together, they reinforced a lesson I will carry forward:
A good backend engineer doesn't just write code. They understand the system they are changing and make that system better.
Top comments (0)