Skip to content

Ever After

Production full-stack app on self-hosted infrastructure.
← Back home
Deployed and running

Wedding Planning Platform

A wedding is a series of events with different guest lists, venues and schedules, so guests are assigned to each Event through a join table rather than invited once to the wedding as a whole. Angular 21 with Signals, NestJS with Prisma, one Nx workspace, running under Docker Compose on a Synology NAS.

  • 11 wedding event types
  • Per-event guest lists
  • Nx workspace, shared types
  • Two containers on a NAS

The product

Running against a seeded demo plan. The couple, guests, and vendors are invented.

Ever After dashboard with a countdown, budget, guest and task tiles, and a list of upcoming events
One plan spans several events, each with its own guest list, budget and vendors. The dashboard rolls them up and surfaces what is blocked.
RSVP dashboard showing invited, responded, attending, declined and pending counts, plus an invitation funnel
Invitations are tracked as a funnel (generated, sent, opened, responded) because the useful question is where a household stalled, not how many replied.
Public guest-facing RSVP form addressed by token, with per-guest and per-event responses and meal preferences
The guest-facing form, reached by a per-household token rather than a login. One household answers for every member, per event, with meal and dietary detail.

Architecture

One workspace, two apps, one shared types library imported by both. Module boundaries are lint-enforced, so the API cannot reach into Angular code.

Nx workspace

  • apps/web: Angular 21 SPA
  • apps/api: NestJS API
  • libs/shared-types: DTOs and enums, imported by both

Trade-off: One version of every dependency: an Nx upgrade lands on both apps whether or not the frontend was ready for it.

Domain Model

14 Prisma models across 6 domain groups.

PlanningEventsGuestsVendorsFinanceTasks

Planning

Events

Guests

Vendors

Finance

Tasks

Select a model for its fields and relationships.

Event Types

Types are rows, not a fixed schedule: a wedding adds the ones it needs.

Ceremony

Full guest list, one venue, officiant

Reception

Seating chart, caterer, bar, DJ

Rehearsal Dinner

Subset of the guest list

Bridal Shower

Own guest list and budget line

Bachelor/ette Party

Separate lists per side

Brunch

Out-of-town guests, next morning

Sangeet

Performance slots as assignments

Mehndi

Artist booked as a vendor

Photo Session

Named guests, no catering

Welcome Event

Arrivals, travel-day logistics

After-Party

Late slot, second venue

Decisions

Event as the unit of scoping

Guests are assigned to specific Events rather than invited once to the Wedding.

Not everyone at the reception was at the rehearsal dinner. Scoping the guest list to each Event through EventAssignment makes that difference data instead of a special case in the UI. The cost lands on the join: EventAssignment carries role and seat metadata rather than being a plain many-to-many, and every guest screen resolves through it.

Feature Detail

Budget

Categories hold the allocation. Payments hold the truth: spend is summed from them rather than stored on the category and kept in sync.

  • Allocation per category, as a percentage of the total or a fixed amount
  • Spend derives from Payments, so a deleted payment cannot leave a stale total
  • Contingency reserve held outside the category allocations
  • Optimistic update in the UI, reconciled against the server response

Stack

Frontend

Angular 21SignalsStandalone ComponentsDeferrable ViewsTailwind CSSTypeScript

Backend

NestJSPrisma ORMSQLitePassport.jsGoogle OAuth 2.0JWT (httpOnly)

DevOps

Nx MonorepoDocker ComposeNginxSynology NAS

Shared

TypeScript (shared types)ESLintPrettierZod (validation)