Tipsy To GoEvent Management Platform Backend
A NestJS and PostgreSQL backend for an event management platform, with Stripe payment integration and Firestore-backed real-time chat.
- NestJS
- TypeScript
- PostgreSQL
- TypeORM
- Redis
- Firebase Firestore
- Stripe
- REST APIs
Project snapshot
- Project type
- Event Management Platform Backend
- My role
- Backend Developer
- Contribution
- Backend Development
- Project context
- Professional Project
Project Overview
Tipsy To Go is an event management platform. Its backend holds the events, the user records and the relationships between them, and it coordinates the two external services the product depends on: Stripe for payments and Firebase Firestore for real-time chat.
The business problem is coordination. An event has people attached to it, money attached to it and conversation attached to it, and all three have to stay consistent with each other — a payment Stripe never confirmed must not read as paid, and a conversation about an event has to be reachable by the people actually attached to that event.
I contributed to this project as a Backend Developer, as part of my employment. My work covered the server side — event and user APIs, the PostgreSQL data model, authentication, Stripe payment integration and the Firestore-backed chat functionality — rather than the complete product, which was built by a team.
What stands out
- Event and user management REST APIs
- Stripe payment integration with state driven by reported outcomes
- Firestore-backed real-time chat alongside the REST API
My Role
Backend Developer — Professional Contribution
I worked on the backend: NestJS modules and REST endpoints for events and users, the PostgreSQL data model through TypeORM, authentication and authorisation, Redis-supported caching, Stripe payment integration and the Firestore-backed real-time chat functionality.
The product is owned by my employer and was delivered by a team. This case study documents the backend responsibilities I held within it, not ownership of the platform.
What I was responsible for
- NestJS modules and REST endpoints for event and user domains
- PostgreSQL schema and TypeORM entities, relations and migrations
- Authentication and authorisation on protected endpoints
- Event workflow logic on the server side
- Stripe payment integration and the payment state it drives
- Firebase Firestore integration for real-time chat
- Redis-supported caching for repeatedly requested data
Services Delivered
The work actually performed on this project, and nothing beyond it.
Backend Development
Server-side application structure, modules and business logic in NestJS and TypeScript.
API Development
REST endpoints for event and user operations, consumed by the platform's client applications.
Database Design
A PostgreSQL model for events, users and their relationships, expressed as TypeORM entities.
Authentication & Authorisation
Sign-in handling and authorisation checks in front of protected endpoints.
Payment Integration
Stripe integration, with the platform's payment records following what Stripe reports rather than what a request assumed.
Real-Time Communication
Firestore-backed chat, so messages reach connected clients without the API being polled for them.
Third-Party API Integration
Stripe and Firebase wired in as separate, configured integrations rather than scattered SDK calls.
Business Workflow Development
Event workflow rules applied on the server, so every client sees the same behaviour.
Functionality
How the system works in practice, workflow by workflow.
Event creation and management
Events are created and updated through backend endpoints, with the server validating and recording them. The event record is what payments, participation and conversation all hang off.
User accounts and interactions
User records and their relationships to events are held on the backend, so what a given user may see or do about a given event is answerable from stored data rather than from client state.
Event-related backend operations
The screens in the client applications are backed by REST endpoints that apply the platform's rules before anything is persisted, which keeps behaviour identical across clients.
Chat communication
Chat runs on Firebase Firestore, with clients subscribing to conversation data directly and receiving updates as they are written. The NestJS API remains the authority for events, users and payments, while Firestore carries the message stream.
Payment processing
Payments are handled through Stripe. The platform records the outcome Stripe reports for a payment rather than assuming a request succeeded, so what the database calls paid is what actually cleared.
Authentication and authorisation
Callers authenticate before reaching protected endpoints, and authorisation runs ahead of business logic, so event and payment operations cannot be reached by simply knowing a URL.
Data management
Events, users and payment records are modelled relationally in PostgreSQL through TypeORM, with validation at the API boundary keeping malformed data out.
Redis-supported functionality
Data that is read repeatedly and changes rarely is served from Redis, so recurring reads do not become recurring database queries.
Key Features
What the system does, and what each capability is worth to the business running it.
Event management APIs
Endpoints for creating, updating and retrieving events, with the platform's rules applied server-side.
Event workflow management
The states an event moves through, handled on the backend so every client reads the same progression.
User and customer management
User records and their relationships to events, kept as the reference for what each user may access.
Authentication and authorisation
Protected routes with authorisation applied before any logic that reads or changes data.
PostgreSQL data model
Events, users and payment records modelled relationally with TypeORM entities and migrations.
Redis caching
Repeatedly requested, rarely changing data served from Redis to keep recurring reads off the database.
Real-time chat via Firestore
Firestore carries the message stream, so clients receive conversation updates as they happen instead of polling an endpoint.
Stripe payment integration
Payments processed through Stripe, with the platform's records following the outcome Stripe reports.
REST backend APIs
One NestJS module per domain, so event, user, payment and integration concerns stay separable.
Challenges & Approach
The engineering problems this project actually presented, why each was difficult, how it was approached and what changed as a result.
Real-time chat alongside a request/response API
- The problem
- Chat needs messages to arrive as they are sent. A REST API answers questions when asked, so making it carry a conversation means either polling it constantly or building a second delivery mechanism.
- Why it was difficult
- Introducing a real-time store means the product now has two places data can live, which raises the question the architecture has to answer clearly: what belongs where, and how records in one stay identifiable from the other.
- Approach
- Firestore was used for the message stream, with clients subscribing to it directly, while the NestJS API stayed the authority for events, users and payments. Identifiers shared between the two keep a conversation tied to the event and users it belongs to.
- Outcome
- Messages reach connected clients without the API being polled, and the relational data that has to be authoritative stays in PostgreSQL rather than being split across stores.
Keeping payment state and application state consistent
- The problem
- Stripe is the authority on whether money moved; the platform database is the authority on what was booked. If the application marks something paid because a request looked successful, those two records can disagree — and the disagreement is about money.
- Why it was difficult
- Payment flows have intermediate and failed states, and calls to an external processor can succeed, fail or be interrupted. Treating a submitted payment as a completed one is the easy mistake to make and the expensive one to find later.
- Approach
- Payment records on the platform side are driven by the outcome Stripe reports for a payment, so the application's notion of paid is derived from the processor rather than assumed at request time. The integration is confined to its own module.
- Outcome
- The platform does not carry records marked as paid that Stripe never confirmed, and payment handling stays in one place rather than spreading through event logic.
Modelling events, users and payments relationally
- The problem
- An event has users attached to it, payments attached to those users, and conversation attached to the event. Each of those relationships is a query someone will need, and the wrong model turns all of them into application-side assembly.
- Why it was difficult
- Event data is naturally many-to-many and the same records are read from several directions — by event, by user, by payment — so the schema has to serve more than one access pattern without duplicating data.
- Approach
- The PostgreSQL schema was designed around those relations and expressed as TypeORM entities, with migrations keeping schema and code in step as the model developed.
- Outcome
- The common questions about an event are answerable in a query, and the database enforces the relationships rather than leaving them to application discipline.
Depending on several external services at once
- The problem
- Stripe and Firebase each bring their own SDK, credentials and failure modes. Called directly from wherever they are needed, they spread configuration and error handling across the codebase.
- Why it was difficult
- Every external dependency is a part of the system that can fail for reasons the application cannot see, and the more places it is called from, the more places have to handle that.
- Approach
- Each integration was given its own module and configuration, so credentials and client setup live in one place per service and the rest of the application calls a local interface rather than a vendor SDK.
- Outcome
- Integration concerns stay contained, and a change in how a service is reached is a change in one module instead of a search through the codebase.
Consistent authentication across API and chat
- The problem
- Users authenticate against the platform, but chat data lives in Firestore. Identity has to mean the same thing in both places, or a user can be someone in one system and someone else in the other.
- Why it was difficult
- Authorisation also has to run before business logic on every protected route, consistently enough that adding an endpoint does not mean re-deciding how it is protected.
- Approach
- Authentication and authorisation were handled in front of the route handlers at the framework level, with user identity from the platform used as the reference for chat participation.
- Outcome
- Protected endpoints are protected by how they are declared, and the identity a user has in chat is the identity the backend issued them.
Technologies Used
The stack this project runs on, grouped by the job each part does.
Backend
- NestJS
- TypeScript
- REST APIs
Data
- PostgreSQL
- TypeORM
- Redis
Integrations
- Stripe
- Firebase Firestore
Cross-cutting
- Authentication & authorisation
- DTO validation
Technical Highlights
- NestJS backend architecture with one module per domain
- Relational PostgreSQL modelling through TypeORM entities and migrations
- Firestore-backed real-time communication alongside a REST API
- Stripe integration with payment state derived from reported outcomes
- Redis-supported caching for repeated reads
- REST API development with DTO-defined contracts
- Authentication and authorisation applied ahead of business logic
- Event management business logic held on the server
Results & Impact
What changed in practice. No figures are quoted here, because none are published for this project.
Event operations handled through APIs
Events, users and their relationships are managed through endpoints that apply the same rules to every client, instead of behaviour differing by app.
Payment records that match the processor
The platform's payment state follows what Stripe reports, which keeps the database from claiming money moved when it did not.
Conversation without polling
Chat is delivered through Firestore subscriptions, so real-time messaging does not turn into constant requests against the API.
Reliable backend and API architecture
Domain modules, boundary validation and framework-level authorisation give the client applications a predictable surface to build on.
Contained integrations
Stripe and Firebase are configured and called in one place each, so external dependencies stay reviewable instead of diffuse.