Full‑stack web application to create, book, and manage events with secure authentication using JWT stored in HttpOnly cookies.
Back end built with Java/Spring Boot, front end with Angular/TypeScript. The REST API is documented with OpenAPI/Swagger.
- Overview
- Features
- Architecture
- Tech stack
- Prerequisites
- Quick start (full‑stack)
- Back end configuration
- Front end configuration
- Authentication and security
- API and documentation
- Usage examples (cURL)
- Project structure
- Build, tests, and run
- Deployment
- Troubleshooting
- Contributing
- License
- Front end: modern, responsive Angular app with reusable components, typed services, and route guards for navigation security.
- Back end: Spring Security + JWT (HttpOnly cookies), user/event/reservation management, and PDF ticket generation.
User:
- Browse and search events
- Secure sign‑up and sign‑in
- Book tickets for events
- Download tickets as PDF
- Manage profile and personal information
- Change password and email
Admin:
- Manage users (roles:
USER,ADMIN) - Manage events (create, update, delete)
- Manage reservations
Cross‑cutting:
- Secured REST API (Spring Security, JWT via HttpOnly cookie)
- OpenAPI/Swagger documentation
- File storage (uploads) and PDF ticket generation
-
Back end (Java/Spring Boot)
controller/: REST controllers (auth, users, events, reservations)model/: JPA entities (User,Event,Reservation)repository/: Data access (Spring Data JPA)service/: Business logic (users, events, reservations, files)security/: Security config, JWT, auth filtersdto/: Data transfer objectsmapper/: Entity ↔ DTO mappers- Storage folders:
tickets/(generated PDFs),uploads/(uploaded files)
-
Front end (Angular/TypeScript)
- Routed pages, reusable components, and services
- Models (TypeScript interfaces), guards (auth/roles), and interceptors (auth, errors)
- Responsive navigation and modular structure (clear separation of pages/components/services)
Back end:
- Java 17+
- Spring Boot
- Spring Security (JWT, HttpOnly cookies)
- JPA/Hibernate
- Maven, Lombok
- Swagger/OpenAPI
Front end:
- Angular
- TypeScript
- RxJS
- Angular Router, Forms, HttpClient
- Java 17 or newer
- Maven (or Maven Wrapper
./mvnw) - Node.js LTS + npm
- (Optional) Angular CLI (
npm i -g @angular/cli) - A database (PostgreSQL/MySQL/H2)
- Clone the repository:
git clone <REPOSITORY_URL>
cd <PROJECT_DIR>- Back end: configure the database in
src/main/resources/application.properties(see below), then start:
./mvnw spring-boot:run
# API available at http://localhost:8081- Front end: install and run Angular dev server:
cd frontend
npm ci
# Recommended in dev: run with a proxy to avoid CORS and handle cookies
npm run start
# or: ng serve --proxy-config proxy.conf.json
# App available at http://localhost:4200- Access:
- Web app: http://localhost:4200
- Swagger UI: http://localhost:8081/swagger-ui.html
Example src/main/resources/application.properties:
# ========= Server =========
server.port=8081
# ========= Datasource =========
spring.datasource.url=jdbc:postgresql://localhost:5432/events
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
# ========= JWT / Security =========
app.jwt.secret=replace_me_with_a_strong_random_secret
app.jwt.expiration=3600000 # 1h (ms)
app.jwt.refresh-expiration=1209600000 # 14d (ms)
app.security.cookie-name=access_token
app.security.cookie-secure=false # true in production (HTTPS)
app.security.cookie-same-site=Lax # Lax/Strict/None (None requires HTTPS)
# ========= CORS (if not using Angular proxy) =========
app.cors.allowed-origins=http://localhost:4200
app.cors.allowed-methods=GET,POST,PUT,PATCH,DELETE,OPTIONS
app.cors.allowed-headers=*
app.cors.allow-credentials=true
# ========= File storage =========
app.storage.uploads-dir=uploads
app.storage.tickets-dir=tickets
# ========= Swagger =========
springdoc.api-docs.enabled=true
springdoc.swagger-ui.enabled=trueNotes:
- Use a strong
app.jwt.secret(32+ chars, random). - In production, enable HTTPS and set
cookie-secure=true. If using cross‑site cookies, setSameSite=None.
- Environments (example):
// frontend/src/environments/environment.ts (development)
export const environment = {
production: false,
// Option A (recommended with proxy): use a relative path
apiBaseUrl: '/api',
// Option B (no proxy): point directly to the back end
// apiBaseUrl: 'http://localhost:8081/api',
};// frontend/src/environments/environment.prod.ts (production)
export const environment = {
production: true,
// Prefer a relative path if front and API share the same domain
apiBaseUrl: '/api',
};- Dev proxy (to avoid CORS and properly forward cookies):
// frontend/proxy.conf.json
{
"/api": {
"target": "http://localhost:8081",
"secure": false,
"changeOrigin": false,
"logLevel": "debug"
}
}Commands:
# package.json
# "start": "ng serve --proxy-config proxy.conf.json"
npm run start- HTTP interceptor to always send cookies (
withCredentials):
// frontend/src/app/interceptors/auth.interceptor.ts
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const withCreds = req.clone({ withCredentials: true });
return next.handle(withCreds);
}
}Register it:
// frontend/src/app/app.config.ts (Angular standalone) or app.module.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AuthInterceptor } from './interceptors/auth.interceptor';
export const appConfig = {
providers: [
provideHttpClient(withInterceptors([() => new AuthInterceptor()])),
]
};- Guards (example):
// frontend/src/app/guards/auth.guard.ts
import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isAuthenticated()) return true;
router.navigate(['/login']);
return false;
};- Downloading PDF tickets:
this.http.get(`${environment.apiBaseUrl}/reservations/${id}/ticket`, {
responseType: 'blob',
withCredentials: true
}).subscribe(blob => saveAs(blob, `ticket-${id}.pdf`));- Authentication via JWT stored in an
HttpOnlycookie set by the back end onPOST /api/auth/login. - Angular cannot read the cookie (HttpOnly) — this is expected and safer. Use
withCredentials: trueso the browser sends the cookie automatically. - Typical public routes:
POST /api/auth/register,POST /api/auth/login, Swagger (/v3/api-docs/**,/swagger-ui.html). - Protected routes require a valid JWT; use
ROLE_USER/ROLE_ADMINdepending on the endpoint. - Token refresh if an endpoint like
/api/auth/refreshexists (implementation dependent).
- Base path:
/api - Swagger UI:
http://localhost:8081/swagger-ui.html - OpenAPI JSON:
http://localhost:8081/v3/api-docs
Use Swagger to explore endpoints, DTO schemas, and request/response examples.
Register:
curl -i -X POST http://localhost:8081/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"email":"user@example.com",
"password":"Password123!",
"fullName":"Jane Doe"
}'Login (sets an HttpOnly cookie):
curl -i -X POST http://localhost:8081/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"Password123!"}'Create an event (using cookies with cURL):
# 1) Login and save cookies
curl -i -c cookies.txt -X POST http://localhost:8081/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"AdminPass123!"}'
# 2) Create event
curl -i -b cookies.txt -X POST http://localhost:8081/api/events \
-H "Content-Type: application/json" \
-d '{
"title":"Spring Conference",
"description":"All about Spring",
"location":"Paris",
"startDate":"2025-09-10T09:00:00",
"endDate":"2025-09-10T17:00:00",
"capacity":150,
"price":49.99
}'Book tickets:
curl -i -b cookies.txt -X POST http://localhost:8081/api/reservations \
-H "Content-Type: application/json" \
-d '{"eventId":1,"quantity":2}'Download a PDF ticket:
curl -L -b cookies.txt -o ticket-123.pdf \
http://localhost:8081/api/reservations/123/ticketPayloads and paths may vary — see Swagger for the authoritative reference.
.
├─ src/
│ └─ main/
│ ├─ java/
│ │ └─ com/
│ │ └─ manu/
│ │ └─ template/
│ │ ├─ controller/
│ │ ├─ dto/
│ │ ├─ mapper/
│ │ ├─ model/
│ │ ├─ repository/
│ │ ├─ security/
│ │ └─ service/
│ └─ resources/
│ └─ application.properties
│
├─ frontend/
│ ├─ src/
│ │ ├─ app/
│ │ │ ├─ pages/ # routed pages (events, details, login, profile, admin, etc.)
│ │ │ ├─ components/ # reusable components (cards, forms, layout…)
│ │ │ ├─ services/ # services (auth, events, reservations, user)
│ │ │ ├─ models/ # TS interfaces (User, Event, Reservation…)
│ │ │ ├─ guards/ # guards (auth, role)
│ │ │ ├─ interceptors/ # interceptors (auth withCredentials, errors)
│ │ │ └─ shared/ # shared modules/utilities
│ │ ├─ assets/
│ │ └─ environments/
│ │ ├─ environment.ts
│ │ └─ environment.prod.ts
│ ├─ proxy.conf.json
│ ├─ package.json
│ └─ angular.json
│
├─ tickets/ # generated PDF tickets
├─ uploads/ # uploaded files
├─ mvnw / mvnw.cmd
├─ pom.xml
└─ README.md
Back end:
# Dev
./mvnw spring-boot:run
# Build jar
./mvnw clean package
java -jar target/app.jar
# Tests
./mvnw testFront end:
cd frontend
# Dev (with proxy)
npm run start
# or: ng serve --proxy-config proxy.conf.json
# Lint
npm run lint
# Unit tests
npm run test
# Production build
npm run build
# Artifacts in: frontend/dist/<app-name>/Scenario 1 — Front and API on the same domain (recommended):
- Serve the API under
/api(reverse proxy Nginx/Traefik -> backend:8081). - Serve Angular build (
frontend/dist) as static files (e.g., Nginx). - In Angular
environment.prod.ts:apiBaseUrl: '/api'. - Cookies:
Secure=true,SameSite=Noneif cross‑subdomain; otherwiseLaxmay be sufficient.
Scenario 2 — Separate domains:
environment.prod.ts: absoluteapiBaseUrl(e.g.,https://api.example.com/api).- Enable CORS on the back end with
allow-credentials=trueandallowed-origins=https://app.example.com. - Cookies:
Secure=true,SameSite=None, optionallyDomain=.example.comfor subdomain sharing.
Spring Boot static option:
- You can copy the Angular build into
src/main/resources/staticto serve the front directly via Spring Boot. Configure SPA fallback at the server/proxy level.
-
CORS/401:
- In dev, use the Angular proxy (
proxy.conf.json) andwithCredentials. - In prod, verify
allowed-origins,allow-credentials, and cookieSameSite/Secure.
- In dev, use the Angular proxy (
-
Cookie not sent:
- Call the API from the same origin (proxy) or enable
withCredentials. - Ensure
Set-Cookieisn’t blocked by CORS. - In HTTPS production,
Secure=trueis required whenSameSite=None.
- Call the API from the same origin (proxy) or enable
-
Tickets/uploads not generated:
- Check write permissions for
tickets/anduploads/. - Verify configured paths and PDF generation services.
- Check write permissions for
-
Front cannot reach API:
- Check
environment*.ts(apiBaseUrl) and reverse proxy configuration.
- Check
Logs:
- Adjust
logging.level.*inapplication.propertiesand inspect Angular console/network logs.
Contributions are welcome:
- Create a feature branch from
main - Write clear commits and keep tests up to date
- Follow code style (lint)
- Open a PR describing your changes