What problem does this solve?
A real estate consultant and content creator needed a site that did more than present them. It had to take bookings and take payment, which turns a marketing site into an application with money in it.
What constraints did it have to work inside?
One developer. That was the constraint, and it was me.
A real payment flow with a client's business running through it rules out any moving part I could not reason about alone at eleven at night. It also changes what a bug costs: a failure in the payment path is not an inconvenience, it is a booking that never happened and a client who finds out before I do.
What did I build?
A React 18 single-page app with React Router v6, MUI v5 including the X date pickers, Emotion for styling, and date-fns and Day.js for date handling.
A Node and Express 4 backend that both serves the built SPA and exposes the API on the same origin.
Payments through the Stripe Node SDK, with Checkout Sessions created server-side.
Booking notifications through Mailchimp Transactional. Input hardened with
express-mongo-sanitize, and configuration through dotenv with the .env file
gitignored.
I provisioned the EC2 instance myself and managed TLS directly with Let's Encrypt and Certbot.
Decisions
Create Checkout Sessions server-side and verify webhook signatures with
constructEvent. The amount has to be decided somewhere the client cannot
influence, which means the server. And an unverified webhook endpoint is a public
URL that will fulfil an order for anyone who can post plausible JSON to it.
Signature verification is what makes the fulfilment path trust the event rather
than the request.
Serve the SPA and the API from the same origin. One Express process serving the built bundle and the API means no CORS configuration, no preflight on every call, and cookies that work without cross-site exceptions. For a single-developer project, removing an entire category of configuration is worth more than the flexibility of splitting them.
Self-manage TLS on EC2 instead of hiding behind a managed platform. I owned the box and the certificate lifecycle. That is more responsibility than a managed host and, honestly, more than the project strictly needed. It is also the reason I know what certificate renewal looks like when it fails, rather than only knowing that it usually does not.
Outcome
Shipped, live, taking real payments, and it ran without a single technical issue.
The business behind it closed a while later and the site is no longer up. I want to be straight about that rather than let a dead link speak for me: the build was never the thing that failed here. There are no traffic or revenue numbers to show you, because the business dissolved before there was meaningful usage to measure. What I can tell you is that the engineering held.
Stack
- React 18
- React Router v6
- MUI v5
- Node.js
- Express 4
- Stripe
- Mailchimp Transactional
- AWS EC2
- Let's Encrypt
