· 2 min read

A small business site with a real CMS

by · Project: Gerilim Elektrik

#react #node #postgres

Gerilim Elektrik is an electrical and energy contractor in Dursunbey, Balıkesir. They do low and medium-voltage work, solar installations, agricultural irrigation systems, and construction and interior projects. They needed a Turkish site that presents those services and a gallery they can keep updated themselves after every finished job.

The public site

The front end is a React and TypeScript single-page app styled with Tailwind:

  • Five pages: home, about, services, projects and contact, plus privacy, cookie and legal notice pages.
  • A project gallery with a lightbox that works with clicks, taps, arrow keys and Escape.
  • Contact through phone and WhatsApp links rather than a form.
  • Structured data and per-page metadata for local search.
  • A short splash animation that reveals the site.

One API, two runtimes

Content and the gallery are served by a Node.js API. Locally it runs as a normal server; in production the same code runs serverless, so there is no second code path to keep in sync.

Storage follows the same idea. The API talks to a small storage interface, and behind it is either a local file for development and tests or Postgres in production. Switching is one setting.

Admin panel

The owner signs in to an admin panel where they can edit page sections, publish or unpublish them, and upload, reorder and caption gallery images.

A CMS for one business still needs two safeguards that are easy to skip.

Conflicting edits

Every piece of content carries a version. An update has to say which version it started from, and if someone else saved in between, the API rejects it instead of silently overwriting their change.

Duplicate submissions

A retried request, for example after a dropped mobile connection, should not create two gallery items. Every change carries a unique key; a repeat of the same request returns the original result instead of running twice.

Images

Uploaded images go to object storage rather than the server's file system, which does not persist in a serverless setup. Uploads are validated before they are stored and served back through the API.

This was the part that fought back. Storage worked locally but failed once deployed, because the serverless runtime did not hand the storage credentials to the function the way local development did. It took a few iterations to fix, and the upload path now logs a clear error when that context is missing, so the next failure explains itself.

Guard rails

  • Rate limiting on the API, and a request ID on every call so logs from one request can be traced.
  • Request bodies validated before they reach any business logic.
  • Integration tests for the CMS flows, authorization rules and upload validation.

The owner updates the gallery without calling a developer, which was the point of the project.