Ever After
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.



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.
Trade-off: One version of every dependency: an Nx upgrade lands on both apps whether or not the frontend was ready for it.
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.
Wedding is the root, but Event is what most reads filter on. The cost of that is one extra join in almost every query.
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
Backend
DevOps
Shared