Real client
Licas — Online ordering platform
An artisan pastry shop was taking orders by phone and text message. I built them a platform where customers order on their own and the business runs everything from a back-office.
Context
A professional project, as the contracted developer. I worked alone: architecture, data model, backend, frontend, legal documents and production readiness.
The problem
Ordering from an artisan pastry shop is not the same as buying at a supermarket. Some orders are custom cakes that need a quote before a price exists; the rest are catalogue products with fixed prices. Allergens have to be declared. There are peak seasons and periods when the shop closes. And there is one person running all of it, who is not technical and cannot be afraid of pressing a button.
The system had to support two different purchase flows without turning into two separate applications.
Technical decisions
Supabase instead of a backend of my own
I chose managed Postgres, with authentication and access policies included, over writing an API from scratch. The gain was time: working alone, a custom backend meant weeks on authentication, permissions and infrastructure before the first useful feature. The cost is vendor dependency, and having to think about security inside the Postgres model rather than in application code — less familiar, and less forgiving.
Permissions enforced in the database, not the interface
Hiding a button is not security. Every access rule is enforced at the database level, and the interface merely reflects what the server has already decided. A user who tampers with the frontend gains nothing. The cost is that every new feature forces you to think about permissions before thinking about screens, which slows development down. It is worth it: a permissions mistake in a shop holding real customer data is not a bug, it is an incident.
Coupons with atomic redemption
The first version of the coupon system had a problem that only shows up with bad luck. I solved it with a uniqueness constraint at the database level rather than a check in code. The difference matters: a check in code can be defeated by two simultaneous requests; a database constraint cannot be defeated by anything.
Allergens with three states, not two
There was a distinction the initial model could not represent. A product with no declared allergens is not the same as a product nobody has checked: the first is information, the second is the absence of it. I separated the two states in the data model. It looks like a detail, but in a food business it is the difference between "this cake contains no nuts" and "we don't know whether this cake contains nuts".
What went wrong
The hardest part was not technical.
Through the build, the client kept changing his mind: things he had asked for that stopped making sense, features he wanted removed, others that appeared halfway through. Often it fell to me to propose how an idea could work — some proposals he accepted, others were not what he had in mind.
The difficulty was understanding what the client wanted when what he described was not quite it. I learned to show rather than ask: build the thing quickly, put it in front of him, and let what was on the screen correct what he had not managed to explain.
It ended well, and the system turned out the way he wanted it.
Outcome
Finished and waiting for the client's go-ahead to publish. The system is ready; the decision to open it to the public is his.
What I'd do differently
I would design the permission model before writing the first screen, instead of growing it as features appeared. Going back to add access rules to an already-built system cost me several rewrites that an afternoon of design up front would have avoided.