Skip to content

About

Photography/Booking website

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Avsa Studio

Avsa Studio is a full-stack photography booking platform built with the PERN stack. Clients can explore photography services, review pricing and locations, and submit a session request. A protected admin workspace allows the studio to review requests, update booking statuses, and manage business content.

The visual direction combines an editorial portfolio with a practical booking system, so the application works as both a marketing website and an internal studio tool.

Project goals

  • Give clients a clear way to discover and request photography services.
  • Prevent invalid, past, or conflicting booking requests.
  • Provide administrators with protected booking and content management.
  • Demonstrate a complete PERN data flow and RESTful CRUD architecture.
  • Keep the frontend, API, and database separated so each layer can evolve independently.

PERN stack

PERN describes the four main technologies used by the project:

  • PostgreSQL stores bookings, services, availability, locations, reviews, gallery records, and contact messages.
  • Express defines the REST API, middleware, validation, and error handling.
  • React renders the customer website, booking flow, navigation, and admin workspace.
  • Node.js runs the Express server and database scripts.

Vite is used as the React development and production build tool.

Architecture

The project follows a three-layer architecture:

Browser / React
      │
      │ HTTP + JSON
      ▼
Express REST API
      │
      │ Parameterized SQL
      ▼
PostgreSQL

1. Presentation layer

The React frontend is responsible for:

  • Rendering pages and reusable UI components.
  • Managing form and navigation state.
  • Performing browser-side validation.
  • Sending API requests with fetch.
  • Displaying success and error feedback.
  • Storing the temporary admin token in sessionStorage.

The frontend never connects directly to PostgreSQL. It communicates exclusively through the Express API.

2. Application layer

The Express backend is responsible for:

  • Defining REST endpoints.
  • Validating incoming request data.
  • Protecting administrative operations.
  • Checking booking conflicts and availability.
  • Executing parameterized database queries.
  • Returning consistent JSON responses and HTTP status codes.
  • Passing unexpected errors to centralized error-handling middleware.

This keeps business rules out of the React components and prevents database credentials from being exposed to the browser.

3. Data layer

PostgreSQL is responsible for persistent and relational data. The schema uses:

  • Primary keys for record identity.
  • Required fields and data types.
  • A booking-status constraint.
  • Rating validation for reviews.
  • A unique availability constraint.
  • A partial unique index for confirmed booking slots.
  • Timestamps for booking and contact history.

Project structure

avsa-studio/
├── frontend/
│   └── src/
│       ├── api/          # Browser API request helpers
│       ├── assets/       # Photography and visual assets
│       ├── components/   # Reusable navigation, cards, and forms
│       ├── pages/        # Customer and admin page views
│       ├── App.jsx       # Navigation and page composition
│       ├── main.jsx      # React entry point
│       └── styles.css    # Responsive editorial design system
├── backend/
│   ├── api/              # Express resource and booking routes
│   ├── db/
│   │   ├── client.js     # PostgreSQL connection pool
│   │   ├── schema.sql    # Database schema and constraints
│   │   └── seed.js       # Schema initialization and seed data
│   ├── middleware/
│   │   ├── auth.js       # Admin token authorization
│   │   └── errorHandler.js
│   └── server.js         # Express application entry point
└── .env.example

CRUD implementation

CRUD stands for Create, Read, Update, and Delete. These operations are exposed through RESTful API endpoints.

CRUD operation HTTP method Example endpoint Project use
Create POST /api/bookings Submit a booking request
Read GET /api/services Display available services
Update PUT /api/bookings/:id Change a booking status
Delete DELETE /api/gallery/:id Remove a gallery record

The resource API supports CRUD for:

  • Services
  • Locations
  • Availability
  • Gallery entries
  • Reviews
  • Contact messages

Bookings have a specialized controller because they require additional validation and conflict detection.

Booking CRUD flow

When a client submits the booking form:

  1. React collects the name, email, service, date, time, location, and notes.
  2. The frontend sends a POST /api/bookings request containing JSON.
  3. Express checks required fields and rejects past dates.
  4. PostgreSQL obtains a transaction-level lock for the requested slot.
  5. The API checks existing inquiries, confirmed bookings, and unavailable times.
  6. If the slot is valid, the booking is inserted and the transaction is committed.
  7. Express returns the created booking with 201 Created.
  8. React displays confirmation to the client.

The transaction and advisory lock prevent two simultaneous requests from reserving the same location and time.

Admin CRUD flow

Administrative requests include:

Authorization: Bearer <ADMIN_TOKEN>

The authorization middleware compares the supplied token with the server-side environment value. Protected operations include:

  • Reading customer booking records.
  • Updating booking statuses.
  • Deleting bookings.
  • Creating, updating, or deleting business resources.

Public clients can read published business data and submit booking or contact requests without receiving administrative access.

API overview

Resource Base endpoint Public behavior Protected behavior
Bookings /api/bookings Create Read, update, delete
Services /api/services Read Create, update, delete
Locations /api/locations Read Create, update, delete
Availability /api/availability Read Create, update, delete
Gallery /api/gallery Read Create, update, delete
Reviews /api/reviews Read Create, update, delete
Contacts /api/contacts Create Read, update, delete

All database values are passed through PostgreSQL query parameters. Resource and column names come from server-defined allowlists rather than user input.

Security and data integrity

  • Admin endpoints require a bearer token.
  • The token is stored in an environment file instead of source control.
  • SQL values use parameterized queries.
  • Writable resource fields are restricted by allowlists.
  • Booking statuses are validated in both Express and PostgreSQL.
  • Booking creation uses a database transaction.
  • Concurrent requests for the same slot use an advisory lock.
  • Confirmed booking slots have an additional database uniqueness rule.
  • CORS restricts browser requests to the configured frontend origin.
  • Helmet adds standard HTTP security headers.
  • Errors are handled by centralized middleware.

For a production system, the token-based admin login would be replaced with user accounts, password hashing, short-lived sessions or JWTs, role-based authorization, rate limiting, and CSRF protection where applicable.

Running locally

Prerequisites

  • Node.js
  • npm
  • PostgreSQL

1. Create the database

createdb avsa_studio

2. Configure the environment

cp .env.example backend/.env

Update backend/.env:

DATABASE_URL=postgresql://localhost/avsa_studio
CLIENT_URL=http://localhost:5173
PORT=3001
ADMIN_TOKEN=replace-with-a-long-random-secret

3. Install dependencies

npm run install:all

4. Create and seed the tables

npm --prefix backend run db:seed

5. Start the application

npm run dev
  • Frontend: http://localhost:5173
  • API health check: http://localhost:3001/api/health
  • Admin workspace: http://localhost:5173/admin

Sign into the admin workspace with the value configured as ADMIN_TOKEN.

Production build

npm run build

The compiled frontend is written to frontend/dist.

How to explain this project in an interview

Short version

Avsa Studio is a PERN photography booking platform. React provides the client-facing portfolio, booking form, and protected admin interface. Express exposes RESTful CRUD endpoints and contains validation and booking rules. PostgreSQL stores the relational business data and enforces additional constraints. I separated the presentation, application, and data layers so the UI never accesses the database directly. One problem I specifically handled was concurrent booking requests, using a PostgreSQL transaction and advisory lock to prevent the same time slot from being requested twice.

Technical decisions to discuss

Why PostgreSQL?

The application contains structured, related business records that benefit from constraints, transactions, and reliable conflict checking. PostgreSQL provides stronger data integrity than keeping this information only in frontend state or flat files.

Why separate booking routes from generic resource routes?

Services and gallery records mostly need standard CRUD behavior. Booking creation has business-specific rules such as date validation, availability checks, conflict detection, and transactions, so it has a dedicated controller.

How is the frontend separated from the backend?

React communicates through HTTP and JSON. API helper modules centralize the backend URL, headers, response parsing, and error handling. The Express server owns all database access and business rules.

How are race conditions handled?

The API creates a transaction and requests a PostgreSQL advisory lock derived from the booking date, time, and location. Requests for the same slot are therefore checked sequentially rather than both passing the conflict query at the same moment.

What would you improve next?

  • Replace the shared admin token with account-based authentication.
  • Add automated unit and integration tests.
  • Add migrations for versioned schema changes.
  • Store uploaded gallery images in object storage.
  • Connect services and bookings through foreign keys.
  • Calculate conflicts using service duration rather than only start time.
  • Add email confirmation and payment processing.
  • Add pagination, filtering, and an audit log to the admin workspace.

Current tradeoffs

  • Admin authorization is intentionally lightweight for this portfolio project.
  • Navigation uses the browser History API without an additional routing dependency.
  • Booking conflicts currently compare the selected start time and location.
  • Service names and locations are stored on booking records as snapshots rather than foreign keys.
  • The UI includes temporary image slots that will be replaced with final portfolio photography.

About

Photography/Booking website

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages