What it is
A university business incubator takes in early-stage startups, walks them through a structured programme, and eventually graduates them. Doing that involves tracking dozens of startups at different stages, assigning mentors, running events, measuring progress, and reporting all of it to the university.
Before this system, most of that lived in spreadsheets and in the heads of the people running the incubator.
This is the system that replaced them. It is also the subject of my undergraduate thesis at Eduardo Mondlane University, which means it is the only project I have built where I had to formally justify every design decision in writing, to two supervisors.
The problem, stated properly
The incubator’s real problem was not “we need a database”. It was that nobody could answer questions about the portfolio as a whole. How many startups are in pre-incubation right now? Which ones have not met a mentor in two months? Which cohort graduated the most companies? Who is behind on their milestones?
Any individual answer was findable if you asked the right person and opened the right spreadsheet. None of them were findable in aggregate, and none of them would still be findable next year, after the person who knew had moved on.
That reframing changed what I built. The interesting part of this system is not the CRUD screens, it is the reporting layer, and the data model that makes the reporting possible.
Modelling the incubation lifecycle
The core decision was to model the startup journey as explicit phases rather than as a status text field:
application → training → pre-incubation → incubation → graduation / demo dayEach transition is a recorded event, not just an overwritten value. That sounds like a small distinction and it is the whole thing. Because transitions are records, you can ask how long startups typically spend in pre-incubation, which is exactly the sort of question the incubator could never answer before.
A status column tells you where a startup is now. A phase history tells you how the programme actually performs.
NOTEThis is the pattern I keep reaching for years later: when someone asks for a status field, ask whether they will eventually want to know how the status changed over time. They almost always will.
Configurable metrics, because every incubator counts differently
The second decision that survived contact with reality: performance metrics are configurable data, not code.
My first instinct was to hardcode the indicators the incubator described to me: revenue, headcount, funding raised. Then I realised that a different incubator, or the same incubator two years later, would want different ones. And since the thesis argues this system should be applicable beyond one institution, hardcoding would have undermined the argument I was making on paper.
So metrics are defined as records. A manager creates an indicator, sets its type and unit, and attaches it to the phases where it applies. Startups then report values against it. Adding a new indicator became an afternoon of data entry rather than a release.
The same reasoning applies to milestones, which are configurable per programme rather than fixed in the schema.
What is actually in it
- Startup management: registration, profiles, phase tracking, documents, and the history of every transition.
- Mentor management: mentor profiles, areas of expertise, and assignment to startups.
- Applications: prospective startups apply through a public form, and the submission becomes a candidate record. No retyping from email.
- Performance tracking: configurable indicators and milestones, reported per startup and aggregated per cohort.
- Statistical dashboards: the portfolio view the incubator did not have before. Distribution by phase, by sector, by cohort, and progress against milestones.
- Event calendar: integrated scheduling for training sessions, mentoring meetings and demo days.
- Role-based access: incubator managers see everything, and an incubated startup sees only its own records. This separation is enforced on the server, not by hiding menu items.
How it is built
Django with a REST API on the backend, Vue 3 and Quasar on the frontend, over PostgreSQL.
The stack choice was partly pragmatic. Django’s admin gave me a working data-entry interface on day one, which meant incubator staff could start putting real data in long before the frontend was finished. That turned out to be the most useful consequence of the choice: real data early exposes modelling mistakes that fake data hides.
Quasar gave me a component library dense enough for admin screens, which is what most of this system is. Tables, forms, filters and dashboards.
The thesis also demanded formal documentation, so this project has something none of my others do: a complete set of use cases, class diagrams, sequence diagrams and state diagrams, written in PlantUML. Drawing the state diagram for the incubation lifecycle is what forced me to notice that two of my transitions were ambiguous.
What went wrong
Feature creep was real, and I lost to it for a while. Every conversation with a stakeholder produced another idea, and for a stretch I said yes to all of them. The system got broader and less finished at the same time. What eventually helped was writing the thesis chapters, because a scope you have to defend in writing is a scope you start defending in code.
I was learning the framework while designing the architecture. This was one of my first Quasar projects and the early code shows it. I barely separated components, and several files grew into genuine messes. I read the documentation and tried to use every feature I could find, which is a normal thing to do and produces abnormal code. Later projects are much tidier, precisely because of this one.
I underestimated reporting. I treated dashboards as the last feature rather than as the core requirement, which is backwards given the problem statement above. Retrofitting aggregate queries onto a model designed for individual records cost me more than designing for both from the start would have.
What I would do differently
- Design the reporting queries first. If the point of the system is aggregate answers, the schema should be validated against those questions before any screen exists.
- Put authorization in the database. I enforced roles in the application layer. Everything I have built since uses row level security in Postgres instead, so a mistake in one view cannot leak data.
- Separate components from the first commit, instead of promising myself a refactor later.
- Ship a smaller version sooner. The incubator would have got real value from startup tracking and the phase dashboard alone, months before the rest existed.
Status
The system is deployed and reachable, and the written thesis is heading for defence. It is the oldest project on this site that I still consider representative of how I think, mostly because of the modelling decisions rather than the code.
I cannot open-source it, since it holds real data about real companies. The diagrams and the reasoning are the parts worth sharing anyway.






