What I Learned From Using AI to Build the MyBreastFriend Backend
When MyBreastFriend went into QA, there were two minor bugs in the backend. Just two. Anyone who has worked on production software will find that hard to believe, but it's true.
I built the backend API for MyBreastFriend using Claude Code extensively throughout the project. As an app that processes personal, health-related data, security and compliance were critical concerns; exactly the kind of project many developers would avoid using AI tools for.
But AI tools can introduce security vulnerabilities, right? Well, so can humans. And in my experience, humans introduce far more, even if they claim they don't.
For most people, using AI tools is about saving time. For me, it's about increasing code quality and security first and foremost (though it often saves me time too). I put the same amount of time into this project as I would have without AI, but the quality of the end product was significantly better than what I could have produced alone within the same timeframe.
Whilst I didn't really get too involved in the front end (my talented colleague, Darren Sarre pieced that together), much of the advice here is as equally applicable to the frontend as it is to the backend.
Here are the key lessons that I took away from the process:
Build Feature by Feature
Early on, I spent some time experimenting with a "one-shot" style approach, dumping a comprehensive prompt into Claude Code and letting it generate the entire application in one go.
In parallel, I also had OpenAI’s codex tool running the same thing so I could compare their results.
They both did a reasonable job, but looking at the generated code it was clear there was a lot more work to do - lots of missing or half built features, code duplication and security issues - to get the code to a place where it was ready to ship.
As such I abandoned my initial attempt to build the app with a oneshot prompt, and started building it out feature by feature; a fundamentally superior approach for production-grade applications.
Why is this approach better?
Building incrementally means every piece of functionality is validated before the next layer is added. You're not debugging a tangled web of interdependent code; instead, you're verifying one clearly-scoped feature at a time, building a framework of elegant, reusable classes validated at every stage. When something breaks, you know exactly where to look because you just added it and you have a complete model of the application in your head.
Starting with solid foundations and building up systematically produces a cleaner, more maintainable codebase. Each feature is built with awareness of what came before, creating natural consistency rather than forcing refactoring passes later. The architecture emerges from considered decisions rather than being imposed all at once.
Most importantly: you understand every line of code from the moment it's written. No massive clean-up phase, no reconciling what the AI thought you meant with what you actually need. Code reviews happen in real-time, problems are caught immediately, and you maintain complete control over architectural decisions.
Getting started
Set up your core structure first - framework version, database, folder structure, naming conventions, unit testing, and documentation. Often I'll do this outside of Claude Code, giving it a clear foundation on which to build. From there, start Claude Code in the directory and run /init. Claude will then review your base architecture and generate a CLAUDE.md file, helping it to maintain context across multiple sessions.
Next, use Claude Code to plan out and implement your authentication system and your encryption layer. For example do you need to encrypt data before saving down to the db? If so, it's important to decide on how this is going to work early on.
Review everything that has been implemented, make sure there is good unit test coverage, then you can start iterating.
The feature delivery loop
For each feature you build, follow this cycle:
1. Write your unit tests (TDD methodology)
Writing unit tests is one of Claude Code's strongest capabilities. When you're surrendering a large chunk of app functionality to AI, unit tests become incredibly important. They give Claude Code a way to actually execute and test the code it has written. Without them, it struggles and starts coding blind, which introduces more errors.
For every feature:
Ask Claude to write the tests first.
Review the tests it has written and work with it to correct any logic or close any key gaps.
Ask Claude to write the functionality to make the tests pass, making sure it runs and validates the tests as it goes.
With MyBreastFriend, I consistently achieved and continue to maintain around 80% test coverage, which is the benchmark for robust, production-grade applications. On top of core functional tests, ask Claude to build you security-related tests to ensure, for example, that routes remain appropriately secured.
2. Code Audit
Never merge code you don't understand, even if it works. It's tempting to just let Claude Code do its thing and trust the output, but this is basically vibe coding, and this approach is not fit for production-grade apps.
Read and understand every line of code that Claude Code writes. You can do this at runtime as it creates your app, however I found it far more useful to wait until it had completed a pass and then review the uncommitted changes via GIT. This approach was a lot more effective; and believe me, without this approval step, a lot more issues would have slipped through the net.
As well as checking functionality, carry out regular security audits, both manually and using Claude Code.
3. Refactor
One of the frustrating things with tools like Claude Code is that they often struggle with DRY (Don't Repeat Yourself) coding principles. They will scatter methods across different controllers that do very similar things, repeating code multiple times. The end result often works, but with untold horrors lurking under the bonnet.
Fortunately, refactoring is where these tools excel. When you notice duplication, refactor immediately while context is fresh. Ask Claude Code to carry out an in-depth code audit looking for opportunities to reduce duplication and improve maintainability, and it will deliver results to a very high standard.
4. Update Seed Data
With each new feature, update your seed data to cover the new functionality and edge cases. Ask Claude Code to extend your seeders to include scenarios that test the feature you just built including edge cases, validation failures, and boundary conditions. Rich, comprehensive seed data makes testing more thorough and catches issues you might otherwise miss.
5. Commit
Once you're satisfied with a chunk of work, ask Claude to commit the changes. It will write a beautifully structured commit message to go along with it.
Commit regularly. It helps maintain clean documentation and gives you an audit trail of how the AI contributed at each stage. And if Claude Code does go rogue and messes up a large chunk of your application (I have seen it happen), you can always revert to previous commits without losing too much work.
6. Update your documentation
At the end of each cycle, ask Claude Code to update the human-readable project documentation. This typically takes the form of a README.md file, or for larger projects it might be a subfolder containing more separate .md files. It's incredibly useful to have this up to date both for yourself and for future engineers working on the project. At the same time, ask Claude to update its own documentation, which lives in CLAUDE.md. This helps with Claude Code’s context, speeding it up and improving the quality of its output.
Keep scope tight
Claude Code's rapid development pace can be overwhelming. Reviewing and validating what it writes becomes a challenge in itself and requires significant concentration.
More importantly, when you have a tool this powerful, there's a strong temptation to keep building - adding features just because you can, not because you need them. Resist this urge, or you will struggle to keep on top of everything it is creating.
Keep the scope tight and the problem you are trying to solve clearly defined. Focus on one feature at a time, building what your application actually needs rather than what's possible.
Be willing to learn
The more senior an engineer is, often the more convinced they are of their own prowess. I will often speak to engineers who refuse to use AI to its maximum potential because they believe they can write better code, and that the AI makes too many mistakes for it to be useful. Both of these statements will be true in some cases, but most of the time they are falsehoods.
Don't go into these projects thinking you are better than the AI. Treat it like an equal coding partner, and you'll get loads more out of it.
I've been building Laravel applications for over a decade now, and I've probably learned more about its capabilities on this one project than I have on any project that came before it. Claude Code will often take my suggestion for an architecture and suggest improvements that make it more scalable, or libraries that I can lean on that I wasn't previously aware of. For example, Claude Code introduced me to the Pest testing framework (so much more useful than PHPUnit), Larastan and Laravel Pint, all of which produce a much better quality codebase.
Conclusion
Two bugs at launch. That result didn't come from letting AI work unsupervised, it came from combining AI capabilities with disciplined engineering practices.
Using Claude Code to build the MyBreastFriend backend showed me what these tools are actually good for. They don't remove the need for careful engineering, clear scope or solid testing, instead they raise the standard of what you can produce with the limited time and resources available to you.
The workflow becomes more disciplined, not less. Code reviews become more thorough. Testing becomes more comprehensive. The result is cleaner code, stronger security and far fewer surprises when you go live.
AI won't replace the need to think, review or understand your own application, but used properly it can help you deliver a safer, more reliable product than you could manage on your own. The two-bug launch proves it.