EventFlow is a production-grade REST API for event management and ticket sales, built with NestJS, Prisma, PostgreSQL, and Redis. Organizers (role ADMIN) create events with multiple ticket types, and users (role USER) buy tickets. Stock is handled safely under concurrency — even when several people try to buy the last ticket at the same time.
⚠️ This is an independent portfolio project. Despite its event-ticketing domain, it is not affiliated with, endorsed by, or connected to Ticketmaster or any third-party ticketing service. It does not call any external ticketing API — it is a self-contained backend.
Keywords: NestJS, TypeScript, REST API, Prisma, PostgreSQL, Redis, JWT auth, RBAC, concurrency control, rate limiting, Swagger/OpenAPI, Docker, Google Cloud Run, CI/CD.
- API: https://eventflow.giovanni-moreno.com/api
- Docs (Swagger): https://eventflow.giovanni-moreno.com/docs
- Health: https://eventflow.giovanni-moreno.com/api/health
Direct Cloud Run URL (same service):
- API: https://ticketmaster-api-223728820566.us-central1.run.app/api
- Swagger: https://ticketmaster-api-223728820566.us-central1.run.app/docs
Deployed on Google Cloud Run (API + Redis sidecar) with Cloud SQL (PostgreSQL). See deploy/README.md.
- NestJS (TypeScript) with a modular architecture
- PostgreSQL + Prisma as ORM and migrations
- Redis for listing cache and distributed rate limiting
- JWT + Passport for authentication and role-based access control
- Swagger for interactive API documentation
- Jest + Supertest for unit and e2e tests
- Docker / Podman for infrastructure and deployment
flowchart LR
Client[Client / Postman] -->|HTTP + JWT| API
subgraph API[NestJS API]
Auth[Auth + Guards]
Events[Events]
Orders[Orders]
Health[Health]
end
API -->|Prisma| PG[(PostgreSQL)]
API -->|cache + rate limit| Redis[(Redis)]
Every request passes through a logging interceptor and the ThrottlerGuard
(rate limiting). Protected routes validate the JWT with JwtAuthGuard and, when
applicable, the role with RolesGuard.
erDiagram
User ||--o{ Event : organizes
User ||--o{ Order : places
Event ||--o{ TicketType : has
TicketType ||--o{ Ticket : issues
Order ||--o{ Ticket : contains
| Entity | Key fields |
|---|---|
User |
email, password (hash), name, role (USER/ADMIN) |
Event |
title, description, venue, startsAt, published |
TicketType |
name, priceCents, capacity, available |
Order |
status (PENDING/PAID/CANCELLED), totalCents |
Ticket |
code (unique) |
Prices are stored in cents (integers) to avoid floating-point errors.
Requires Node 20+ and Podman (or Docker).
# 1. install dependencies
npm install
# 2. copy environment variables and adjust if needed
cp .env.example .env
# 3. start postgres and redis
podman compose up -d # or: docker compose up -d
# 4. apply migrations and generate the prisma client
npm run prisma:deploy
npm run prisma:generate
# 5. (optional) load sample data
npm run prisma:seed
# 6. start the API
npm run start:devThe API runs at http://localhost:3000/api, interactive docs at
http://localhost:3000/docs, and the health check at
http://localhost:3000/api/health.
To bring up the entire stack (including the API in a container):
podman compose --profile full up -d --buildImport docs/ticketmaster-api.postman_collection.json. The Login request
stores the token automatically; the create-event requests store eventId and
ticketTypeId to chain the purchase flow.
| Role | Password | |
|---|---|---|
| Admin | admin@ticketmaster.test | admin1234 |
| User | user@ticketmaster.test | user1234 |
The critical case is two users trying to buy the last ticket at the same time.
The purchase runs inside a transaction that locks the ticket-type row with
SELECT ... FOR UPDATE. The second transaction waits for the first to finish
and then sees the already-updated stock, preventing oversell. If no stock
remains, it returns 409 Conflict. An e2e test verifies this by firing two
simultaneous purchases of the single available ticket.
A global request limit per time window (configurable via THROTTLE_TTL and
THROTTLE_LIMIT) backed by Redis, so it stays consistent even with multiple
API instances.
Listings of published events are cached in Redis for 30 seconds and invalidated when an event is created, edited, or deleted.
npm test # unit tests
npm run test:cov # with coverage
npm run test:e2e # end-to-end tests (requires infra up)Tests cover oversell prevention, password hashing, login, the roles guard, date validation, and the full flow against real PostgreSQL and Redis.
This project is licensed under the MIT License — see the LICENSE file for details.
