JUBI Ceramics: Full Stack E-Commerce Website

JUBI Ceramics: Full Stack E-Commerce Website

Developed a fully custom e-commerce website for JUBI Ceramics, a local Portuguese brand focused on handcrafted ceramic pieces. This project was one of my most technically demanding and rewarding experiences, as I was fully responsible for the entire software development lifecycle: from architecture and design to deployment and domain configuration.

The website is now live at jubi.pt, and I invite you to explore it, especially if you're a fan of unique, high-quality ceramic pieces. I truly believe in the creative potential of this local project, which is why I personally committed to helping bring it to life with the best tools and care possible.

JubiCeramics logo

Key Highlights

  • Full-stack development using PHP (Laravel) with TailwindCSS for a modern, clean UI.
  • PostgreSQL for robust, scalable data storage.
  • Dockerized architecture to ensure consistent environments and easy maintenance.
  • Nginx configuration for optimized web serving, HTTPS handling, and reverse proxy.
  • Let's Encrypt integration for automatic TLS certificates.
  • Custom dashboard for product management, orders, and stock.
  • Self-hosted on a Linux VPS with manual setup of server environment.

Impact

  • Empowered a local artist to sell products independently without relying on third-party platforms.
  • Improved business control over branding, stock, and customer experience.
  • Gave me valuable experience in server configuration, Laravel development, Docker orchestration, and deployment pipelines.

This project challenged me across multiple domains - backend, frontend, DevOps, and UI design and helped me grow tremendously as a developer.

This video showcases the main features of the website.

Update: security improvements

A while after launch, the order form started attracting some uninvited guests: bots hammering it with automated submissions. They never actually got anything useful, but every attempt fired off a confirmation email to the address on the "order", which isn't a great look and isn't a great use of anyone's inbox. So I sat down and gave the site a proper security pass, one layer at a time.

1. Server layer (Nginx), the first line of defense, before a request ever reaches Laravel.

  • Global rate limiting to shut down aggressive scanners and keep resource usage sane.
  • A much stricter limit of 3 requests per minute per IP on the POST /order endpoint. Try to machine-gun the order form and you get an instant 503 from Nginx, no PHP involved.

2. Frontend layer, catching the lazier bots at the door.

  • Cloudflare Turnstile (a friendlier alternative to reCAPTCHA) to confirm there's a human on the other end, without annoying real customers.
  • The submit button starts disabled and only lights up once Turnstile gives the green light, so blind scripted submissions have nothing to click.
  • A honeypot: a hidden field that real users never see. Dumb bots fill in every field they find in the HTML, so they walk right into it.

3. Backend layer (Laravel), for the sophisticated ones that slip past Nginx and forge requests directly.

  • Server-side Turnstile validation: the controller checks the token against Cloudflare's Siteverify API, so a faked widget doesn't cut it.
  • Silent honeypot: if that hidden field comes back filled, the request dies with a 403. No logging, no noise, these attacks just quietly evaporate instead of filling my disk.
  • A tiny homemade WAF: regex checks across every text field looking for classic SQL injection and XSS patterns (<script, SELECT, DROP, and friends). Also fails silently.
  • Integer overflow prevention: the view routes (/piece/{id}, /pack/{id}) now constrain IDs to a max of 10 digits via regex, so scanners can't overflow PostgreSQL's integer range and drown the logs in SQLSTATE[22003] errors.

None of the attacks ever succeeded, but "it didn't work for them" was still bugging me ahahah. Now bot noise is much more reduced.