An API for booking events with built-in protection against race conditions and duplicate bookings. A single user cannot book twice for the same event.
- Overview
- Features
- Tech Stack
- Prerequisites
- Installation
- Configuration
- Database Setup
- Running the Application
- API Documentation
- Testing
- Project Structure
- Error Handling
- Database Schema
- Transaction Management
- Contributing
- License
The Reservation System API is a Node.js/Express backend application that manages event bookings with robust safeguards against common issues in concurrent systems. It ensures data integrity through PostgreSQL transactions and prevents overbooking and duplicate user reservations.
- Event Booking Management: Create and manage event bookings through RESTful endpoints
- Duplicate Prevention: Automatically prevents users from booking the same event multiple times
- Concurrency Control: Uses database row-level locking to prevent race conditions and overbooking
- Transaction Support: All booking operations are wrapped in database transactions with automatic rollback on failure
- Input Validation: Validates all required fields before processing bookings
- Error Handling: Comprehensive error handling with meaningful HTTP status codes
- Comprehensive Testing: Jest test suite with 100% endpoint coverage
- Runtime: Node.js (v18+)
- Framework: Express.js (v5.1.0)
- Database: PostgreSQL (v12+)
- Testing: Jest (v30.2.0) with Supertest (v7.1.4)
- Language: JavaScript (ES6+)
- Transpiler: Babel (v7.28.4)
- Environment Management: dotenv (v17.2.3)
Before you begin, ensure you have the following installed:
- Node.js (v18 or higher)
- PostgreSQL (v12 or higher)
- npm (v9 or higher)
- Git
Follow these steps to set up the project locally:
- Clone the repository
git clone https://github.com/saadakhtr29/Reservation-System-API.git
cd Reservation-System-API- Install dependencies
npm installThis will install all required dependencies specified in package.json:
- Express.js for the web framework
- PostgreSQL driver for database connectivity
- Jest and Babel for testing and transpilation
- Other development dependencies
The application uses environment variables for configuration. Two environment files are supported:
Create a .env file in the project root:
DATABASE_URL=postgresql://user:password@localhost:5432/reservation_db
PORT=8000
NODE_ENV=developmentCreate a .env.test file in the project root for testing:
DATABASE_URL=postgresql://user:password@localhost:5432/reservation_test_db
PORT=8001
NODE_ENV=test- DATABASE_URL: Connection string to your PostgreSQL database in the format:
postgresql://username:password@host:port/database_name - PORT: Port number on which the server will listen (default: 8000)
- NODE_ENV: Environment flag for distinguishing between development and test environments
Important: The .gitignore file is configured to exclude both .env and .env.test files from version control to protect sensitive credentials.
Ensure PostgreSQL is running and accessible. If you don't have PostgreSQL installed, download it from postgresql.org.
- Connect to PostgreSQL
psql -U postgres- Create the development database
CREATE DATABASE reservation_db;- Create the test database
CREATE DATABASE reservation_test_db;- Exit PostgreSQL
\qThe database schema is defined in SQL files located in the models/ directory. You need to run these scripts to set up the tables.
For Development:
psql -U postgres -d reservation_db -f models/events.sql
psql -U postgres -d reservation_db -f models/bookings.sqlFor Testing:
psql -U postgres -d reservation_test_db -f models/events.sql
psql -U postgres -d reservation_test_db -f models/bookings.sqlAlternatively, if your application has setup scripts, they may handle this automatically.
npm startThe server will start on the port specified in your .env file (default: 3000).
You should see output similar to:
Server running on port 8000
Once the server is running, verify it's working:
curl http://localhost:8000/Expected response:
Event Booking API is running
http://localhost:8000/api
Endpoint: POST /bookings/reserve
Description: Creates a new booking for a user at an event. Prevents duplicate bookings and overbooking.
Request Body:
{
"event_id": 1,
"user_id": "user123"
}Parameters:
event_id(integer, required): The unique identifier of the eventuser_id(string, required): The unique identifier of the user making the reservation
Success Response (201 Created):
{
"message": "Booking reserved successfully",
"booking": {
"id": 1,
"event_id": 1,
"user_id": "user123",
"created_at": "2024-01-15T10:30:00.000Z"
}
}Error Responses:
- 400 Bad Request: Missing required fields
{
"error": "event_id and user_id are required"
}- 404 Not Found: Event does not exist
{
"error": "Event not found"
}- 409 Conflict: User already booked for this event
{
"error": "User already booked for this event"
}- 409 Conflict: No seats available
{
"error": "No seats available"
}- 500 Internal Server Error: Server-side error
{
"error": "Internal server error"
}Example Usage:
curl -X POST http://localhost:8000/api/bookings/reserve \
-H "Content-Type: application/json" \
-d '{"event_id": 1, "user_id": "user123"}'The project includes a comprehensive test suite covering all endpoints and edge cases.
npm testThis command runs Jest in band mode with the test environment configuration. Output will show all test results with pass/fail status.
The test suite (tests/booking.test.mjs) includes five test cases:
- Successful Booking Creation: Verifies that a valid booking is created successfully with status 201
- Duplicate Booking Prevention: Ensures the same user cannot book the same event twice (status 409)
- Overbooking Prevention: Confirms bookings are rejected when all seats are full (status 409)
- Non-existent Event Handling: Validates proper error handling for non-existent events (status 404)
- Invalid Request Handling: Tests validation of missing required fields (status 400)
Before running tests, ensure you've created the test database and tables as described in the Database Setup section.
The test suite automatically:
- Clears all data before and after each test
- Creates a fresh test event with 2 seats for each test
- Closes the database connection after all tests complete
Reservation-System-API/
├── config/
│ └── db.js # Database connection configuration
├── controllers/
│ └── bookingController.js # Booking business logic
├── models/
│ ├── events.sql # Events table schema
│ └── bookings.sql # Bookings table schema
├── routes/
│ └── bookingRoutes.js # API route definitions
├── tests/
│ └── booking.test.mjs # Test suite for booking endpoints
├── server.js # Express application entry point
├── package.json # Project dependencies and scripts
├── babel.config.cjs # Babel transpiler configuration
├── jest.setup.cjs # Jest test setup and environment config
├── .gitignore # Git ignore rules
└── README.md # This file
config/db.js: Establishes PostgreSQL connection using the pg library with connection pooling for better performance and resource management.
controllers/bookingController.js: Contains the reserveBooking function which handles all business logic for creating bookings, including transaction management, concurrency control, and validation.
models/: SQL schema definitions for database tables with appropriate constraints and relationships.
routes/bookingRoutes.js: Defines all API routes and maps them to their corresponding controller functions.
server.js: Entry point of the application. Initializes Express, configures middleware, and starts the server.
tests/booking.test.mjs: Comprehensive test suite using Jest and Supertest for integration testing.
The application implements comprehensive error handling:
All requests are validated for required fields before processing. Missing fields return a 400 Bad Request status.
The booking controller uses PostgreSQL transactions with row-level locking (FOR UPDATE) to prevent race conditions. This ensures that even with concurrent requests, the system maintains data integrity.
- The
bookingstable has aUNIQUE(event_id, user_id)constraint that prevents duplicate bookings at the database level - The
eventstable has aCHECK (total_seats >= 0)constraint ensuring non-negative seat counts - Foreign key constraints ensure referential integrity between tables
Errors are logged to the console for debugging purposes. In production, consider integrating a proper logging service.
All database transactions automatically roll back on error, preventing partial or inconsistent state changes.
CREATE TABLE IF NOT EXISTS events (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
total_seats INT NOT NULL CHECK (total_seats >= 0)
);Columns:
id: Primary key, auto-incrementing integername: Event name (required, text)total_seats: Maximum number of available seats (required, must be >= 0)
CREATE TABLE IF NOT EXISTS bookings (
id SERIAL PRIMARY KEY,
event_id INT NOT NULL REFERENCES events(id) ON DELETE CASCADE,
user_id VARCHAR NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
UNIQUE(event_id, user_id)
);Columns:
id: Primary key, auto-incrementing integerevent_id: Foreign key referencing events (deleted when event is deleted)user_id: Unique identifier for the user (text)created_at: Timestamp of booking creation (defaults to current time)
Constraints:
UNIQUE(event_id, user_id): Ensures each user can only book once per event- Foreign key with
ON DELETE CASCADE: Automatically removes bookings when an event is deleted
The booking system uses database transactions to maintain consistency:
- Transaction Start:
BEGIN- Starts a new transaction - Row Locking:
FOR UPDATE- Locks the event row to prevent concurrent modifications - Validation: Checks for duplicate bookings and available seats
- Insertion: Adds the booking record
- Commit or Rollback: Either commits all changes or rolls back on error
This approach ensures that booking operations are atomic, consistent, isolated, and durable (ACID properties).
We welcome contributions to improve the Reservation System API. Please follow these guidelines:
- Fork the repository
- Clone your fork locally
- Create a new branch for your feature:
git checkout -b feature/your-feature-name
- Make your changes
- Run tests to ensure everything works:
npm test - Follow the existing code style and conventions
- Add tests for any new functionality
- Commit with clear, descriptive messages:
git commit -m "Add feature: description"
- Push your branch to your fork
- Create a Pull Request to the main repository
- Provide a clear description of your changes
- Link any related issues
- Use consistent indentation (2 spaces)
- Follow ES6+ best practices
- Write meaningful variable and function names
- Add comments for complex logic
- Keep functions focused and modular
- All new features must include tests
- Existing tests must continue to pass
- Aim for high code coverage
Last Updated: January 2025
For questions or support, please open an issue in the repository.