The Monday it became real
On a Friday evening, after everyone had gone home, I cleared the replacement database and ran the full data transfer one final time.
CreateMore handled projects, scheduling, timesheets, holidays and invoicing at INK, the design studio where I worked. The old application had stayed live throughout the rebuild. This last transfer brought its latest data into the system I had spent about six months building. Once it finished, I redirected the old address to the replacement.
Kamen, the studio's owner, had been testing it with me as I built it. He trusted it enough to make the switch. I still wanted to test absolutely everything.
By Monday morning, around twenty people were using it. We had kept the company's LDAP authentication, so their existing logins worked. They opened the same address, signed in as usual and carried on working.
For me, it felt strange. I kept waiting for something to break.
A few problems appeared, but the transition was far smoother than I expected. From that Monday on, it became real company software.
Two weeks that became almost two years
After sixth form, I applied for roughly thirty apprenticeships and got nowhere. Then a family friend mentioned me to a friend of his who ran a design studio. That was Kamen. He offered me two weeks at INK as a test.
During my first few days, I fixed a date-ordering bug on one of the company's websites. It took me minutes. To Kamen, that showed the value of having someone technical inside the company: otherwise, even a small fix could take days of messages with the external web team.
Those two weeks became almost two years. It was my first job as a software engineer, and there was no senior engineer at INK to show me how to structure an application or review my decisions. I had to work things out as I went.
CreateMore eventually became my main project, but I was not the first person asked to replace it. When I arrived, another version was already being built.
The replacement that was already underway
The original CreateMore dated back to around 2010. It used PHP, Zend Framework and ExtJS, and had served the company for more than a decade. By the time I joined, parts of it were starting to break and the company needed someone who could maintain and improve it.
Before I arrived, Kamen had brought in an external team. As he explained it to me, he had asked whether they could build on the existing PHP application. They proposed a replacement using tools they knew, including Vue.js.
As I remember it, that replacement had already been underway for a couple of years when I joined. One of my early tasks was to add a news section to it.
I did not know Vue, so I learnt enough to build it. I made the news page, including creating, reading, editing and deleting posts, and sent them the code. I did not continue working on that version.
A few months into my time at INK, Kamen asked me to build CreateMore myself. The other team continued working on their version while I started mine.
Learning the system while rebuilding it
I started my rebuild in 2024. I had mostly used the old CreateMore to enter timesheets, so when Kamen showed me the rest of the application, I realised how little of it I had seen. Even then, I thought it would take about three months.
I had seen the screens. I had not yet seen all the rules behind them.
Kamen became my guide. He would open a section, show me how he used it and explain what each action should do. I would ask questions, rebuild the behaviour and give him a development version to test. Most workflows were broadly like-for-like, although we changed things when the old behaviour no longer made sense.
I barely looked at the PHP. The old database was the part I studied because the replacement needed the company's existing information. I kept that information, but reorganised some relationships around how I thought the new application should work. Some of those decisions helped. Others left me with problems I would understand better later.
Every month brought something else: a workflow I had not known existed, a change Kamen wanted, or an improvement I wanted to make. At first we were building for INK alone. A couple of months into the build, I learnt that another company would eventually use the same codebase too.
Three months became roughly six. The surprising part was how quickly they passed. I would work on CreateMore for about seven hours, go home, and wonder where the day had gone. I wanted to see the next part connect to everything I had already built.
I chose React, Next.js and Express because they were what I knew. Sequelize gave me a way to work with MariaDB from Node.js. I initially suggested a cloud setup, but Kamen preferred a VPS from a provider he knew. MariaDB was already available on the server, so I used it.
This was also my first complete full-stack application deployed on a Linux VPS. I learnt about processes, TLS, backups and server management, and used GitHub Actions to automate deployments. When I reached something unfamiliar, I worked it out and carried on.
The company chose my version
As my version approached completion, the other team discovered its login page. They had an LDAP account, so they could sign in. They wanted to know where this other CreateMore had come from.
I was sitting beside Kamen when he explained that I had been building it myself. With my version nearly ready, he chose it as the company's replacement. I was nineteen, and being trusted with that responsibility was a huge moment for me.
I later spoke with their main engineer about how the application worked and how I had built it. By then, most of the build was behind me. The next job was moving the company's history into it.
Moving the company's history
I spent about a week writing the transfer scripts.
The order mattered. Clients had to exist before their projects, projects before their invoices, and many records needed an ID created earlier in the transfer. A master script ran the individual scripts in sequence so those relationships could be rebuilt.
Employees were still adding data to the old system while I tested. For each rehearsal, I cleared the destination tables and rebuilt them from the latest source data. Updating only changed records would have meant maintaining a synchronisation process. I expected to run this transfer a handful of times, then retire the old application, so a complete rerun suited the job.
Kamen checked development versions populated with information he knew. The first complete transfer mostly worked, and we ironed out the remaining details through repeated runs. I cannot remember every mismatch now, and the scripts were lost with my old machine.
The final Friday was another full run with the newest data. That was the point when the rehearsals became the company's actual move.
The people at the next desk
Kamen had pushed me to release before I felt completely ready. He had already tested the sections as I built them, but the two of us were never going to find everything the whole office would.
Once people started using it, they found things we had missed almost immediately. I could walk over to someone's desk, see the problem and understand what they had been trying to do. Sometimes I had built something incorrectly. Sometimes I needed to explain a changed workflow. Sometimes their feedback showed us a better way to do it.
Having that context made the next change much easier. In this rollout, with Kamen's testing behind us and me there to respond, real use told us things another month of testing by the same two people would have missed.
What happens when you drag a holiday
The holiday scheduler caused me more grief than any other part of CreateMore.
Bryntum supplied the calendar interface, but almost every interaction depended on our business rules. Who could move an event? Was it pending or approved? Did the employee have enough leave? Was it a full day or only a few hours? Changing dates could also affect allowances, time off in lieu (TOIL) and timesheets elsewhere in the system.
The code I maintain now shows why even dragging a holiday involved several stages. A normal employee can move their own pending holiday; managers and other privileged users have different permissions. Before an annual-leave drop completes, Bryntum pauses it while the application checks the proposed duration against the employee's allowance. That calculation must exclude the holiday being edited, otherwise it would count the same leave twice.
If the check fails, the drop is rejected. If it passes, accepting the calendar change is still only one step: the changed dates need to be sent to the API and saved. When approved leave moves, the server also has to remove the old timesheet allocations and write the hours against the new dates.
That means keeping the visible event, the calendar's internal state and the saved business records consistent. Clicking, holding, dragging, dropping and cancelling each have their own place in the sequence. A box appearing in the right place does not tell me whether all the work behind it has happened.
I remember the pattern more clearly than the individual bugs. Booking, editing, approving and displaying holidays kept revealing cases I had missed, and that area attracted the most complaints. I worked through them with many small patches. Integrating a calendar meant making its interactions fit the rest of the application.
Finding an archive from the project
CAF search came from watching someone look for an old asset. It was a feature I added myself after noticing how the existing process worked.
INK had many terabytes of assets spread across dozens of archive drives. People used separate catalogue-search software to find a file and identify the physical drive containing it. Usually, they already knew which project it belonged to.
That was the useful connection. CreateMore already knew the project, so I put an archive-search button there. Opening a project and pressing that button could take someone straight to its matching archive locations.
The CAF files were catalogues of the drives' contents: names, folder paths, sizes and dates. The assets themselves stayed on the archive drives. CreateMore needed to search those descriptions and tell someone where to look.
My first implementation searched the catalogue files directly. For a request, the backend would run Python searches across the CAF files, reading their contents and looking for matches. It worked, but I remember a search taking roughly ten seconds.
I later moved the searchable entries into MariaDB and added database indexes. A background worker extracted the catalogue data and inserted it in batches. Searches could then query prepared database records instead of going back through the catalogue files each time.
The project button uses the project number as its search term. The database searches the indexed names and paths, and the dialog returns matching folders with their archive names and locations. In use, I saw searches go from that roughly ten-second wait to results arriving in milliseconds. Those are my observations from using it, rather than a controlled benchmark.
I also improved the catalogue listing in the archive settings. Instead of counting database entries separately for every catalogue, I fetched those counts together. Later I cached the listing in server memory for five minutes, avoiding repeated metadata reads when people reopened it. That cache served the catalogue list; the project searches used the database.
People stopped using the previous search application. They said thank you and carried on working. The new workflow became normal almost immediately.
That is one of the parts I am most proud of. I had watched how someone did a job, connected it to information we already had, and made it easier to do from the place they were already working.
Software we could change
As of September 2026, I still maintain CreateMore for one company. It holds more than 2,000 projects, more than 200 accounts have been created over its lifetime, and around twenty people use it day to day.
The biggest change was what happened when someone wanted something different. With the old system, a relatively small alteration could feel unrealistic. With this one, they could show me the problem and we could do something about it. CAF search was one example of that freedom.
We actually had control.
Maintaining the application also made the cost of some early decisions clearer. I had been coding quickly, trying to get the whole system working, and carried arrangements forward that deserved another look.
User and TeamMember are a good example. User came first. As I built more of the application, TeamMember became the employee record used by timesheets, holidays, scheduling, permissions and expenses. User remained mainly the account used to sign in, with a global role and preferences such as the interface theme.
Both held the person's name and email. Creating an employee meant creating both records, and keeping their overlapping information consistent became another responsibility. I had linked the models and kept building instead of deciding clearly where those fields belonged.
I also lacked a reliable history of committed database migrations. The models describe what the application expects today, but the repository cannot reconstruct every schema change that led there. Some controllers grew beyond four thousand lines as HTTP handling, business rules and database operations accumulated together. A small change became harder to reason about when it lived among so many other responsibilities.
Later, I added automated tests around project budget calculations. One test uses two eight-hour timesheet entries, with one task excluded from the budget. For an eight-hour working day and a four-day budget, it expects one day spent and 25% used. It checks the values passed to a mocked database update as well as the API response. That gives a specific business rule a repeatable check when the calculation changes.
Supporting different companies also meant separate deployments with company-specific configuration in the same codebase. I once described that simply as multi-tenancy, but the distinction matters: it was not one shared application and database serving several organisations.
This was my first engineering job. Kamen could tell me whether the software did what the business needed, but I had no senior engineer to point out those design problems as I built. I did the best I could with what I knew. Living with the decisions afterwards taught me where that understanding had been incomplete.
How I would build it today
At YUV, I have since worked with a more structured TypeScript and GCP system and learnt from a senior engineer. That experience, alongside maintaining CreateMore, has changed how I would approach another version.
Give the code and database clearer structure
I would keep one backend, using NestJS and TypeScript on Cloud Run, with modules for projects, finance, people, scheduling and files. Controllers would handle HTTP, services would own the business rules, and database access would follow a consistent pattern. Those boundaries would make a change easier to follow and review.
I would use PostgreSQL on Cloud SQL with Prisma, committing a reviewed migration for every schema change. A new environment should be reproducible from the repository. I would also decide which record owns each piece of identity and employee information before carrying that model across the application.
Make adding another company deliberate
For a version intended to serve more companies, I would choose a shared application and database. Company-owned records would carry a tenant_id, with constraints and relationships scoped to that tenant. The API would take the organisation from verified authentication, and PostgreSQL Row-Level Security would add a second check using a role that cannot bypass its policies and tenant context set within each transaction.
I would use Clerk for identity, map its organisations to CreateMore tenants, and make access invitation-only through Google or Microsoft accounts. A small set of roles—Admin, Manager, Finance and Member—would map to explicit permissions enforced by the API. Tenant isolation would need automated tests and review from the beginning.
Make files and background work independent of one server
Permanent files would live in private Cloud Storage, with tenant-prefixed object paths, metadata in PostgreSQL and short-lived signed URLs for authorised access. CAF entries would remain searchable in the database. Cloud Run's filesystem would be temporary working space for tasks such as generating PDFs.
Cloud Scheduler would trigger scheduled work, Cloud Tasks would handle queued work and retries, and Cloud Run Jobs would handle imports and larger recalculations. For critical operations, I would record the intended work in an outbox within the database transaction and use idempotency keys so retries could be recognised and handled safely.
That would introduce more services, more dependence on GCP and Clerk, and potentially more cost than a VPS at this scale. Sharing a database would also make tenant isolation an ongoing responsibility. I would accept those trade-offs for repeatable environments and easier company onboarding, while keeping the backend together as one application.
What I would keep
I would keep working close to the people using the software. Kamen could explain the business, someone at the next desk could show me a problem, and an ordinary search for an old asset could turn into a feature.
That first Monday was the start of a much longer job: maintaining what people had begun relying on. CreateMore is still in use, and I am still learning from the decisions I made while building it.