I built a consumer web app in the personal growth genre using an AI-assisted workflow that falls slightly short of vibecoding. For context, although I write code as a part of my (not web-related) day job, I'm not a professional full stack developer.
A slightly simplified description of the workflow I used to build the app looks something like this. In order to add a new feature, I would:
Discuss my feature idea with ChatGPT (or Gemini or Claude, free tier in each case) to get a rough sense of what the feature should look like and how, on a high level, it should be implemented.
Tell GitHub Copilot to implement the feature in a new branch.
Debug in the IDE using Copilot in interactive mode.
This is the app's tech stack:
Python FastAPI backend.
Next.js frontend served statically using nginx.
Postgresql database.
Docker compose orchestration.
Stripe integration for paid-tier subscriptions and Brevo integration for transactional email.
Overall I'm pretty happy with the workflow. Here are some of the positives:
The workflow helped me quickly build from zero to some level of functionality across the stack, which in turn allowed me to learn the technologies involved in the context of an already more-or-less working app.
Copilot implemented various features much faster than I would have been able to.
Copilot added various small conveniences and visual goodies that simply would not have occurred to me.
The biggest problem was that Copilot, especially when working non-interactively, is hesitant to revise existing interfaces, preferring to add checks and fall-backs "for compatibility". This is an understandable approach, considering that we don't want it to nuke design choices that are made with some future functionality in mind. Still, it often produces lengthy, unprincipled, and hard-to-read spaghetti code.
Ad-hoc implementation choices sometimes became unintended (bad) design choices, with multiple layers of code duct-taped on top – without my knowledge – to keep things from falling apart. I discovered one such case when I noticed that some backend files implementing API routes were bloated and kept breaking. The cause was ultimately the fact that the app's various API routes didn't have a common prefix (like '/api'). When Copilot implemented the first few routes, there was presumably no need to organize them neatly, and when more functionality was added, the mistake became baked in. Fixing it wasn't particularly hard when I finally became aware of it.
Better prompting might have alleviated some of these problems. One thing I intend to try is adding something like "inform me of any technical debt that made the feature harder to implement cleanly" to my prompts.
I built a consumer web app in the personal growth genre using an AI-assisted workflow that falls slightly short of vibecoding. For context, although I write code as a part of my (not web-related) day job, I'm not a professional full stack developer.
A slightly simplified description of the workflow I used to build the app looks something like this. In order to add a new feature, I would:
This is the app's tech stack:
Overall I'm pretty happy with the workflow. Here are some of the positives:
The biggest problem was that Copilot, especially when working non-interactively, is hesitant to revise existing interfaces, preferring to add checks and fall-backs "for compatibility". This is an understandable approach, considering that we don't want it to nuke design choices that are made with some future functionality in mind. Still, it often produces lengthy, unprincipled, and hard-to-read spaghetti code.
Ad-hoc implementation choices sometimes became unintended (bad) design choices, with multiple layers of code duct-taped on top – without my knowledge – to keep things from falling apart. I discovered one such case when I noticed that some backend files implementing API routes were bloated and kept breaking. The cause was ultimately the fact that the app's various API routes didn't have a common prefix (like '/api'). When Copilot implemented the first few routes, there was presumably no need to organize them neatly, and when more functionality was added, the mistake became baked in. Fixing it wasn't particularly hard when I finally became aware of it.
Better prompting might have alleviated some of these problems. One thing I intend to try is adding something like "inform me of any technical debt that made the feature harder to implement cleanly" to my prompts.