Once upon a time in a bustling city, there was Alex, a sports lover with a big dream. He wanted to create a simple way for people to book sports facilities hassle-free. With his trusty team of developers, they set out to make this dream a reality.
Using TypeScript, Express.js, and Mongoose, they built the backbone of their platform. They created models for users, facilities, and bookings, making sure to include all the necessary details.
Next came the fun part—designing the API endpoints. Users could sign up, log in, and book facilities with ease. Admins had extra powers to manage facilities and view all bookings.
But it wasn't all smooth sailing. They encountered challenges along the way, like handling errors and ensuring security. However, with determination and teamwork, they overcame each obstacle.
After months of hard work, their platform was finally ready. People could now book sports facilities with just a few clicks. Alex and his team celebrated their success, proud of what they had accomplished together. And so, their simple idea turned into a solution that made a big difference in the world of sports.
You are tasked with developing the backend for a sports facility booking platform. This assignment focuses on implementing the following key functionalities: Error Handling, CRUD operations, Authentication & Authorization, and Transaction & Rollback if needed.
- Programming Language: TypeScript
- Web Framework: Express.js
- ODM & Validation Library: Mongoose for MongoDB
User Model:
name
: The name of the user.email
: The contact email address.password
: The account password (must be hashed).phone
: The contact phone number.role
: The role of the user (can be 'admin' or 'user').address
: The physical address.
Facility Model:
name
: The title of the facility.description
: A brief description of the facility.pricePerHour
: The cost of booking the facility per hour.location
: The physical location of the facility.isDeleted
: Boolean indicating if the facility is marked as deleted (false means not deleted).
Booking Model:
date
: The date of the booking.startTime
: The start time of the booking.endTime
: The end time of the booking.user
: Reference to the user who made the booking.facility
: Reference to the booked facility.payableAmount
: The calculated amount payable for the booking.isBooked
: Status of the booking (confirmed, unconfirmed, or canceled).
Formula:
payableAmount = (endTime - startTime) * pricePerHour
Assume endTime
and startTime
are datetime objects and pricePerHour
is a numeric value representing the cost per hour.
Example:
- Facility's price per hour: 20 TK
- Booking start time: 10:00 AM
- Booking end time: 1:00 PM
Calculation:
- Duration: 3 hours
- Payable amount: 3 hours * 20 TK/hour = 60 TK
- User Sign Up
- Route:
POST /api/auth/signup
- Request Body:
{
"name": "Programming Hero",
"email": "web@programming-hero.com",
"password": "programming-hero",
"phone": "01322901105",
"role": "admin", // or 'user'
"address": "Level-4, 34, Awal Centre, Banani, Dhaka"
}
- Response:
{
"success": true,
"statusCode": 200,
"message": "User registered successfully",
"data": {
"_id": "60d9c4e4f3b4b544b8b8d1c4",
"name": "Programming Hero",
"email": "web@programming-hero.com",
"role": "admin",
"phone": "01322901105",
"address": "Level-4, 34, Awal Centre, Banani, Dhaka"
}
}
- User Login
- Route:
POST /api/auth/login
- Request Body:
{
"email": "web@programming-hero.com",
"password": "programming-hero"
}
- Response:
{
"success": true,
"statusCode": 200,
"message": "User logged in successfully",
"token": "JWT_TOKEN",
"data": {
"_id": "60d9c4e4f3b4b544b8b8d1c4",
"name": "Programming Hero",
"email": "web@programming-hero.com",
"role": "admin",
"phone": "01322901105",
"address": "Level-4, 34, Awal Centre, Ban Myeni, Dhaka"
}
}
- Create a Facility (Admin Only)
- Route:
POST /api/facility
- Headers:
Authorization: Bearer JWT_TOKEN
{
"name": "Tennis Court",
"description": "Outdoor tennis court with synthetic surface.",
"pricePerHour": 30,
"location": "456 Sports Ave, Springfield"
}
{
"success": true,
"statusCode": 200,
"message": "Facility added successfully",
"data": {
"_id": "60d9c4e4f3b4b544b8b8d1c5",
"name": "Tennis Court",
"description": "Outdoor tennis court with synthetic surface.",
"pricePerHour": 30,
"location": "456 Sports Ave, Springfield",
"isDeleted": false
}
}
- Update a Facility (Admin Only)
- Route:
PUT /api/facility/:id
- Headers:
Authorization: Bearer JWT_TOKEN
{
"name": "Updated Tennis Court",
"description": "Updated outdoor tennis court with synthetic surface.",
"pricePerHour": 35,
"location": "789 Sports Ave, Springfield"
}
- Response
{
"success": true,
"statusCode": 200,
"message": "Facility updated successfully",
"data": {
"_id": "60d9c4e4f3b4b544b8b8d1c5",
"name": "Updated Tennis Court",
"description": "Updated outdoor tennis court with synthetic surface.",
"pricePerHour": 35,
"location": "789 Sports Ave, Springfield",
"isDeleted": false
}
}
- Delete a Facility - Soft Delete (Admin Only)
- Route:
DELETE /api/facility/:id
- Headers:
Authorization: Bearer JWT_TOKEN
- Response:
{
"success": true,
"statusCode": 200,
"message": "Facility deleted successfully",
"data": {
"_id": "60d9c4e4f3b4b544b8b8d1c5",
"name": "Updated Tennis Court",
"description": "Updated outdoor tennis court with synthetic surface.",
"pricePerHour": 35,
"location": "789 Sports Ave, Springfield",
"isDeleted": true
}
}
6. Get All Facilities
- Route:
GET /api/facility
- Response:
{
"success": true,
"statusCode": 200,
"message": "Facilities retrieved successfully",
"data": [
{
"_id": "60d9c4e4f3b4b544b8b8d1c5",
"name": "Tennis Court",
"description": "Outdoor tennis court with synthetic surface.",
"pricePerHour": 30,
"location": "456 Sports Ave, Springfield",
"isDeleted": false
}
]
}
Check the availability of time slots for booking on a specific date.
- Route:
GET /api/check-availability
- date (
string
, optional): The date for which availability is to be checked. Format:YYYY-MM-DD
. If not provided, today's date will be used by default.
- success (
boolean
): Indicates whether the request was successful. - statusCode (
number
): HTTP status code of the response. - message (
string
): Descriptive message indicating the outcome of the request. - data (
Array
ofObject
): Array containing information about available time slots.
- startTime (
string
): The start time of the available slot. - endTime (
string
): The end time of the available slot.
GET /api/check-availability?date=2024-06-15
{
"success": true,
"statusCode": 200,
"message": "Availability checked successfully",
"data": [
{
"startTime": "08:00",
"endTime": "10:00"
},
{
"startTime": "14:00",
"endTime": "16:00"
}
]
}
Hints:
- Parse the optional
date
query parameter from the request URL. If not provided, use today's date. - Retrieve bookings for the specified date from your database using Mongoose.
- Define a function to find available time range based on the bookings retrieved. Compare booked time range to the total available time slots for the day.
- Return a response containing the available time slots in the specified format. Use
res.json()
to send the response.
8. Create a Booking (User Only)
- Route:
POST /api/bookings
- Headers:
Authorization: Bearer JWT_TOKEN
{
"facility": "60d9c4e4f3b4b544b8b8d1c5",
"date": "2024-06-15",
"startTime": "10:00",
"endTime": "13:00"
}
- Response:
{
"success": true,
"statusCode": 200,
"message": "Booking created successfully",
"data": {
"_id": "60d9c4e4f3b4b544b8b8d1c6",
"facility": "60d9c4e4f3b4b544b8b8d1c5",
"date": "2024-06-15",
"startTime": "10:00",
"endTime": "13:00",
"user": "60d9c4e4f3b4b544b8b8d1c4",
"payableAmount": 90,
"isBooked": "confirmed"
}
}
If the facility is unavailable during the requested time slot, an error response is returned.
9. View All Bookings (Admin Only)
- Route:
GET /api/bookings
- Headers:
Authorization: Bearer JWT_TOKEN
- Response:
{
"success": true,
"statusCode": 200,
"message": "Bookings retrieved successfully",
"data": [
{
"_id": "60d9c4e4f3b4b544b8b8d1c6",
"facility": {
"_id": "60d9c4e4f3b4b544b8b8d1c5",
"name": "Tennis Court",
"description": "Outdoor tennis court with professional-grade surface.",
"pricePerHour": 30,
"location": "123 Main Street",
"isDeleted": false
},
"date": "2024-06-15",
"startTime": "10:00",
"endTime": "13:00",
"user": {
"_id": "60d9c4e4f3b4b544b8b8d1c4",
"name": "Programming Hero",
"email": "programming.hero@example.com",
"phone": "+1234567890",
"role": "user",
"address": "456 Elm Street"
},
"payableAmount": 90,
"isBooked": " confirmed"
}
]
}
10. View Bookings by User (User Only)
- Route:
GET /api/bookings/user
- Headers:
Authorization: Bearer JWT_TOKEN
{
"success": true,
"statusCode": 200,
"message": "Bookings retrieved successfully",
"data": [
{
"_id": "60d9c4e4f3b4b544b8b8d1c6",
"facility": {
"_id": "60d9c4e4f3b4b544b8b8d1c5",
"name": "Tennis Court",
"description": "Outdoor tennis court with professional-grade surface.",
"pricePerHour": 30,
"location": "123 Main Street",
"isDeleted": false
},
"date": "2024-06-15",
"startTime": "10:00",
"endTime": "13:00",
"user": "60d9c4e4f3b4b544b8b8d1c4",
"payableAmount": 90,
"isBooked": " confirmed"
}
]
}
11. Cancel a Booking (User Only)
- Route:
DELETE /api/bookings/:id
- Headers:
Authorization: Bearer JWT_TOKEN
{
"success": true,
"statusCode": 200,
"message": "Booking cancelled successfully",
"data": {
"_id": "60d9c4e4f3b4b544b8b8d1c6",
"facility": {
"_id": "60d9c4e4f3b4b544b8b8d1c5",
"name": "Tennis Court",
"description": "Outdoor tennis court with professional-grade surface.",
"pricePerHour": 30,
"location": "123 Main Street",
"isDeleted": false
},
"date": "2024-06-15",
"startTime": "10:00",
"endTime": "13:00",
"user": "60d9c4e4f3b4b544b8b8d1c4",
"payableAmount": 90,
"isBooked": "canceled"
}
}
When retrieving data, if the database collection is empty or no matching data is found, return the message: "No data found."
{
"success": false,
"statusCode": 404,
"message": "No Data Found",
"data":[]
}
Implement proper error handling throughout the application. Use global error handling middleware
to catch and handle errors, providing appropriate error responses with error messages.
Error Response Object Should include the following properties:
- success → false
- message → Error Type → Validation Error, Cast Error, Duplicate Entry
- errorMessages
- stack
Sample Error Response
{
"success": false,
"message": "E11000 duplicate key error collection: univerity-management.students index: email_1 dup key: { email: \\"user2@gmail.com\\" }",
"errorMessages": [
{
"path": "",
"message": "E11000 duplicate key error collection: project index: email_1 dup key: { email: \\"user2@gmail.com\\" }"
}
],
"stack": "MongoServerError: E11000 duplicate key error collection: project index: email_1 dup key: { email: \\"user2@gmail.com\\" }\\n at H:\\\\next-level-development\\\\project-management-auth-service\\\\node_modules\\\\mongodb\\\\src\\\\operations\\\\insert.ts:85:25\\n at H:\\\\next-level-development\\\\university-management-auth-service\\\\node_modules\\\\mongodb\\\\src\\\\cmap\\\\connection_pool.ts:574:11\\n at H:\\\\next-level-development\\\\university-writeOrBuffer (node:internal/streams/writable:391:12)"
}
Implement a global "Not Found" handler for unmatched routes. When a route is not found, respond with a generic message: "Not Found.”
{
"success": false,
"statusCode": 404,
"message": "Not Found",
}
Implement an Authentication Middleware to authenticate your application. Ensures that only user and admin can access their own accessible routes.
{
"success": false,
"statusCode": 401,
"message": "You have no access to this route",
}
The API employs Zod for input validation, ensuring data consistency. When validation fails, a 400 Bad Request status code is returned, accompanied by detailed error messages specifying the erroneous fields and reasons.