A Node.js Express API for managing seat bookings with distributed locking using Redis. This system prevents double-booking by implementing time-based locks on seats.
- Distributed Locking: Uses Redis to ensure only one user can book a seat at a time
- Time-Based Expiry: Seat locks automatically expire after 60 seconds
- Conflict Detection: Returns clear error messages when seats are already locked
- RESTful API: Simple HTTP endpoints for booking operations
- Node.js with Express 5
- Redis for distributed locking
- CORS enabled for cross-origin requests
- ES Modules for modern JavaScript syntax
4c/
├── index.js # Main application entry point
├── client.js # Redis client configuration
├── controllers/
│ └── booking.controller.js # Booking business logic
├── models/
│ └── seat.model.js # Seat locking data layer
└── routes/
└── booking.routes.js # API route definitions
- Clone the repository:
git clone <repository-url>
cd 4c- Install dependencies with pnpm:
pnpm install- Configure Redis connection in
client.jswith your Redis instance credentials
Development mode (with auto-reload):
pnpm devProduction mode:
pnpm startThe server will start on http://localhost:3000
POST /api/book
Books a seat with a distributed lock that expires in 60 seconds.
Request Body:
{
"seat_number": "A12"
}Success Response (200):
{
"success": true,
"message": "Seat A12 booked successfully. Lock active for 1 minute.",
"bookingId": 1737654321000,
"seat_number": "A12",
"expires_in": 60
}Conflict Response (409):
{
"success": false,
"error": "Seat A12 is currently locked.",
"try_again_in_seconds": 45
}Error Response (400):
{
"success": false,
"error": "seat_number is required"
}- HTTP Request: The client sends a POST request to
/api/bookwith aseat_numberin the JSON body. - Validation: The controller checks that
seat_numberexists. Missing values return a 400 error. - Lock Attempt: The model tries to acquire a Redis lock for the seat using a unique key.
- Success Path:
- If the lock is acquired, a booking response is returned immediately.
- The lock expires automatically after 60 seconds so the seat is released if no further action occurs.
- Conflict Path:
- If the lock already exists, the API fetches the remaining TTL.
- The response includes how many seconds to wait before retrying.
- Error Path: Any unexpected error returns a 500 with a generic message.
- Lock Key Format:
seat_lock:<seat_number>(for example,seat_lock:A12). - Atomic Lock: The lock uses Redis
SETwith theNXflag to ensure it is only created if it does not already exist. - Expiry: The lock uses
EX 60so Redis will automatically delete it after 60 seconds. - TTL Retrieval: If a lock exists,
TTLis used to report how much time remains.
Redis guarantees that the SET with NX is atomic, so only the first request can create the lock. All concurrent requests for the same seat will fail to acquire the lock and receive a conflict response, which eliminates race conditions that would otherwise allow double booking.
The application uses Redis Cloud with the following connection settings:
- Host:
redis-13365.c330.asia-south1-1.gce.cloud.redislabs.com - Port:
13365 - Connection timeout: 20 seconds
Note: Update the credentials in client.js with your own Redis instance.
The project uses path aliases with @/ prefix for cleaner imports:
@/client.js→ Redis client@/models/seatModel.js→ Seat locking functions@/controllers/bookingController.js→ Request handlers
ISC