A simple CRUD REST API to serve a Parking Reservation System built using NodeJS in TypeScript.
This project is the backend for Parking Management System built using Node.js, TypeScript, and SQLite, incorporating Role-Based Access Control (RBAC) to manage user permissions effectively. The system provides a comprehensive RESTful API for user authentication, garage management, parking spot management, parking reservations, and payment processing. The architecture is designed for maintainability and scalability, leveraging built-in Node.js libraries for cryptographic functions and Zod for data validation.
- Parking Reservation System - NodeJS REST API
-
User Authentication:
- Create new accounts
- User login (regular users and garage admins)
- Logout functionality
- Token refresh for maintaining sessions
-
Garage Management:
- Retrieve a list of all garages
- Open a new garage
- Update garage details
- Delete or close a garage
-
Parking Spot Management:
- Retrieve all parking spots
- Add new parking spots
- Update parking spot details
- Delete parking spots
-
Parking Reservation:
- Reserve a parking spot
- Retrieve reservation details
- Update existing reservations
- Cancel reservations
-
Payment Processing:
- Pay reservation fees
- Node.js: JavaScript runtime for building scalable network applications.
- TypeScript: A superset of JavaScript that compiles to plain JavaScript, providing static typing.
- SQLite: A lightweight database engine for local data storage. Supported in NodeJS as a built-in package since Node v22.5.0.
- Crypto: Node.js's built-in library for cryptographic functions, used for password hashing and verification.
- JWT (JSON Web Tokens): For secure user authentication and session management.
- Zod: A TypeScript-first schema declaration and validation library, used for validating user input and API requests.
Role-Based Access Control (RBAC) is a critical component of this Parking Management System. It ensures that users can only access resources and perform actions that are permitted based on their assigned roles. The system currently supports two roles:
- Garage Admin: Users with this role have permissions to manage garages and parking spots, including creating, updating, and deleting them.
- User: Regular users can reserve parking spots and manage their reservations but do not have access to garage management functionalities.
To enforce RBAC, we have created a custom decorator @authorize(roles: string[])
. This decorator can be applied to controller methods to restrict access based on the user's role. The decorator checks the user's role against the required roles for the endpoint.
Here’s how you can use the @authorize
decorator in your controller methods:
import { IncomingMessage, ServerResponse } from 'http';
import authorize from '../../decorators/authorize';
class GarageController {
@authorize([EnumUserRole.USER, EnumUserRole.GARAGE_ADMIN])
public async getGarages(req: IncomingMessage, res: ServerResponse) {
// Logic of this controller method
}
@authorize([EnumUserRole.GARAGE_ADMIN])
public async createGarage(req: IncomingMessage, res: ServerResponse) {
// Logic of this controller method
}
}
In the above example, the createGarage
method can be accessed only by users with the garage_admin
role while the getGarages
method can be accessed by users with the user
or garage_admin
roles.
If a user without the appropriate role attempts to access these endpoints, they will receive a 403 Forbidden
response.
To get started with this project, follow these steps:
-
Clone the Repository:
git clone https://github.com/ricoputrap/node-parking-reservation cd node-parking-reservation
-
Install Dependencies: Make sure you have Node.js v22.5.0 or above and npm installed. Then run:
npm install
-
Set Up Environment Variables: Create a
.env
file in the root directory and add the following environment variables:PORT=8000 CRYPTO_ALGO=aes-256-cbc CRYPTO_KEY=<random string> CRYPTO_IV=<random string> ACCESS_TOKEN_SECRET=<random secret string> REFRESH_TOKEN_SECRET=<random secret string>
- How to generate values for
CRYPTO_KEY
andCRYPTO_IV
- Write a simple JS script below:
// generate-crypto-keys.js const crypto = require('crypto'); const key = crypto.randomBytes(32); const iv = crypto.randomBytes(16); const CRYPTO_KEY = key.toString('hex'); const CRYPTO_IV = iv.toString('hex'); console.log("CRYPTO_KEY:", CRYPTO_KEY) console.log("CRYPTO_IV:", CRYPTO_IV)
- Run the JS script above:
node generate-crypto-keys.js
- Store the generated value of
CRYPTO_KEY
andCRYPTO_IV
in your.env
file.
- Write a simple JS script below:
- How to generate values for
ACCESS_TOKEN_SECRET
andREFRESH_TOKEN_SECRET
- Basically you can put anything inside those two variables.
- How to generate values for
-
Compile TypeScript: Compile the TypeScript files to JavaScript:
npm run build
-
Run the Application: Start the server:
npm start
-
Testing the API: You can use tools like Thunder Client, Postman, or cURL to test the API endpoints.
node-parking-reservation/
├── config/
| ├── constants.ts
| ├── database.ts
| ├── enums.ts
├── scripts/
| ├── db-setup.js
├── src/
| ├── @types/
│ │ ├── http.d.ts
| ├── decorators/
│ │ ├── authorize.ts
| ├── entity/
│ │ ├── garage.entity.ts
│ │ ├── user.entity.ts
| ├── errors/
│ │ ├── BadRequestError.ts
│ │ ├── ForbiddenError.ts
│ │ ├── NotFoundError.ts
│ │ ├── UnauthorizedError.ts
| ├── features/
│ │ ├── auth/
│ │ │ ├── handlers/
│ │ │ │ ├── index.ts
│ │ │ │ ├── login.ts
│ │ │ │ ├── logout.ts
│ │ │ │ ├── refresh.ts
│ │ │ │ ├── register.ts
│ │ │ ├── controller.ts
│ │ │ ├── route.ts
│ │ │ ├── validation.ts
│ │ ├── garages/
│ │ │ ├── handlers/
│ │ │ │ ├── create.ts
│ │ │ │ ├── delete.ts
│ │ │ │ ├── getAll.ts
│ │ │ │ ├── getByAdmin.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── update.ts
│ │ │ ├── controller.ts
│ │ │ ├── route.ts
│ │ │ ├── validation.ts
│ │ ├── parking-spots/
│ │ ├── reservations/
│ │ ├── payments/
| ├── models/
│ │ ├── garage-model/
│ │ │ ├── index.ts
│ │ │ ├── index.types.ts
│ │ ├── user-model/
│ │ │ ├── index.ts
│ │ │ ├── index.types.ts
│ │ ├── types.ts
| ├── stores/
│ │ ├── tokens.ts
| ├── utils/
│ │ ├── http/
│ │ │ ├── index.ts
│ │ ├── logger/
│ │ │ ├── index.ts
│ │ ├── passwordHashing/
│ │ │ ├── index.ts
│ │ ├── token/
│ │ │ ├── index.ts
│ │ │ ├── index.types.ts
│ │ ├── validations/
│ │ │ ├── index.ts
├── index.ts
├── .env
├── .gitignore
├── tsconfig.json
├── package.json
After setting up the project, you can use the API endpoints listed above to manage users, garages, parking spots, reservations, and payments. Each endpoint adheres to RESTful principles, ensuring a consistent and intuitive interface.
You can write unit tests and integration tests for your application using testing frameworks like Jest or Mocha. Ensure that your tests cover all critical functionalities, including user authentication, CRUD operations for garages and parking spots, and reservation management.
Contributions are welcome! If you would like to contribute to this project, please follow these steps:
- Fork the repository.
- Create a new branch (
git checkout -b feature/YourFeature
). - Make your changes and commit them (
git commit -m 'Add some feature'
). - Push to the branch (
git push origin feature/YourFeature
). - Open a pull request.
-
Create New Account
- POST
/api/auth/register
- Request Body:
{ "name": string, "email": string, "password": string, "role": string // EnumUserRole }
- Response:
{ "success": true, "message": "User created successfully", "data": { "id": 1, "name": "John Doe", "email": "[email protected]", "role": "user" // or "garage_admin" } }
- POST
-
Login
- POST
/api/auth/login
- Request Body:
{ "email": string, "password": string }
- Response:
{ "success": true, "message": "Login successful", "data": { "accessToken": "ajsdjkansdkjsandsa.asdlansdk.asdasd" } }
- POST
-
Logout
- POST
/api/auth/logout
- Request Headers:
Authorization: Bearer <accessToken>
- Response:
{ "success": true, "message": "Logout successful" }
- POST
-
Refresh Token
- POST
/api/auth/refresh
- Request Headers:
Authorization: Bearer <accessToken>
refreshToken
httpOnly cookie
- Response:
{ "success": true, "message": "Access token refreshed successfully", "data": { "accessToken": "aksjdnasd.asdasdasd.asdasdsa" } }
- POST
-
Create a New Garage
-
POST
/api/garages
-
Roles: Garage Admin
-
Request Headers:
Authorization: Bearer <accessToken>
-
- Allowed role: Garage Admin
-
Request Headers:
Authorization: Bearer <accessToken>
-
Request Body:
{ "name": "string", "location": "string", "pricePerHour": "number" }
-
Response:
{ "success": true, "message": "Garage created successfully with id 1", "data": { "id": 1, "name": "Downtown Garage", "location": "123 Main St, City", "pricePerHour": 5.25, // in USD "adminID": 1 } }
-
-
Get All Garages
-
GET
/api/garages
-
Roles: Garage Admin and Regular User
-
Request Headers:
Authorization: Bearer <accessToken>
-
Query Params:
name
: stringlocation
: stringstartPrice
: number (decimal)endPrice
: number (decimal)page
: numbersize
: number
-
Response:
{ "success": true, "message": "Successfully fethced 2 garages", "data": [ { "id": 1, "name": "Downtown Garage", "location": "123 Main St, City", "price": 5.25, "active": true, "adminID": 1 }, { "id": 1, "name": "Uptown Garage", "location": "456 Elm St, City", "price": 2.5, "active": true, "adminID": 1 }, ] }
-
-
Update Garage Information
-
PUT
/api/garages/{garageId}
-
Allowed role: Garage Admin
-
Request Headers:
Authorization: Bearer <accessToken>
-
Request Body:
{ "name": "string", "location": "string", "pricePerHour": "number" }
-
Response:
{ "success": true, "message": "Garage updated successfully", "data": { "id": 1, "name": "Downtown Garage", "location": "123 Main St, City", "price": 2.25, "adminID": 1 }, }
-
-
Delete a Garage
- DELETE
/api/garages/{garageId}
- Allowed role: Garage Admin
- Request Headers:
Authorization: Bearer <accessToken>
- Response:
{ "success": true, "message": "Garage deleted successfully" }
- DELETE
-
Add a New Parking Spot
- POST
/api/spots
- Allowed role: Garage Admin
- Request Headers:
Authorization: Bearer <accessToken>
- Request Body:
{ "name": "A1", "garageID": 1, }
- Response:
{ "success": true, "message": "Parking spot added successfully", "data": { "id": 1, "garageID": 1, "name": "A1", "reserved": false, } }
- POST
-
Get All Parking Spots in a Garage
- GET
/api/garages/{garageID}/spots
- Allowed role: Garage Admin and Regular User
- Request Headers:
Authorization: Bearer <accessToken>
- Response:
{ "success": true, "message": "Successfully fetched 2 parking spots in garage 1", "data": [ { "id": 1, "garageID": 1, "name": "A1", "reserved": false, }, { "id": 1, "garageID": 1, "name": "A1", "reserved": false, }, ] }
- GET
-
Update Parking Spot Detail
- PUT
/api/spots/{spotId}
- Allowed role: Garage Admin
- Request Headers:
Authorization: Bearer <accessToken>
- Request Body:
{ "name": "A1 New Name" }
- Response:
{ "success": true, "message": "Parking spot status updated successfully", "data": { "id": 1, "garageID": 1, "name": "A1 New Name", "reserved": false, } }
- PUT
-
Delete a Parking Spot
- DELETE
/api/spots/{spotId}
- Allowed role: Garage Admin
- Request Headers:
Authorization: Bearer <accessToken>
authentication - Response:
{ "success": true, "message": "Parking spot deleted successfully" }
- DELETE
-
Create a Parking Reservation
- POST
/api/reservations
- Headers:
Authorization
:Bearer <token>
// JWT token for user authentication
- Request Body:
{ "userId": 1, "parkingSpotId": 1, "startTime": "2023-10-01T10:00:00Z", "endTime": "2023-10-01T12:00:00Z" }
- Response:
{ "success": true, "message": "Reservation created successfully", "data": { "reservationId": 1, "userId": 1, "parkingSpotId": 1, "startTime": "2023-10-01T10:00:00Z", "endTime": "2023-10-01T12:00:00Z", "status": "confirmed" } }
- POST
-
Get All Reservations for a User
- GET
/api/reservations/user
- Headers:
Authorization
:Bearer <token>
// JWT token for user authentication
- Response:
{ "success": true, "data": [ { "reservationId": 1, "userId": 1, "parkingSpotId": 1, "startTime": "2023-10-01T10:00:00Z", "endTime": "2023-10-01T12:00:00Z", "status": "confirmed" }, { "reservationId": 2, "userId": 1, "parkingSpotId": 2, "startTime": "2023-10-02T10:00:00Z", "endTime": "2023-10-02T12:00:00Z", "status": "confirmed" } ] }
- GET
-
Get Reservation Details
- GET
/api/reservations/{reservationId}
- Headers:
Authorization
:Bearer <token>
// JWT token for user authentication
- Response:
{ "success": true, "data": { "reservationId": 1, "userId": 1, "parkingSpotId": 1, "startTime": "2023-10-01T10:00:00Z", "endTime": "2023-10-01T12:00:00Z", "status": "confirmed" } }
- GET
-
Update a Reservation
- PUT
/api/reservations/{reservationId}
- Headers:
Authorization
:Bearer <token>
// JWT token for user authentication
- Request Body:
{ "parkingSpotId": 2, "startTime": "2023-10-01T11:00:00Z", "endTime": "2023-10-01T13:00:00Z" }
- Response:
{ "success": true, "message": "Reservation updated successfully", "data": { "reservationId": 1, "userId": 1, "parkingSpotId": 2, "startTime": "2023-10-01T11:00:00Z", "endTime": "2023-10-01T13:00:00Z", "status": "confirmed" } }
- PUT
-
Cancel a Reservation
- DELETE
/api/reservations/{reservationId}
- Headers:
Authorization
:Bearer <token>
// JWT token for user authentication
- Response:
{ "success": true, "message": "Reservation canceled successfully" }
- DELETE
-
Initiate Payment
- POST
/api/payments
- Headers:
Authorization
:Bearer <token>
// JWT token for user authentication
- Request Body:
{ "reservationId": 1, "amount": 20.00, "paymentMethod": "credit_card", "cardDetails": { "cardNumber": "string", "expiryDate": "MM/YY", "cvv": "string" } }
- Response:
{ "success": true, "message": "Payment processed successfully", "data": { "paymentId": 1, "reservationId": 1, "amount": 20.00, "status": "completed" } }
- POST
-
Get Payment Details
- GET
/api/payments/{paymentId}
- Headers:
Authorization
:Bearer <token>
// JWT token for user authentication
- Response:
{ "success": true, "data": { "paymentId": 1, "reservationId": 1, "amount": 20.00, "status": "completed", "paymentMethod": "credit_card", "transactionDate": "2023-10-01T10:00:00Z" } }
- GET
-
Get All Payments for a User
- GET
/api/payments/user
- Headers:
Authorization
:Bearer <token>
// JWT token for user authentication
- Response:
{ "success": true, "data": [ { "paymentId": 1, "reservationId": 1, "amount": 20.00, "status": "completed", "paymentMethod": "credit_card", "transactionDate": "2023-10-01T10:00:00Z" }, { "paymentId": 2, "reservationId": 2, "amount": 15.00, "status": "completed", "paymentMethod": "paypal", "transactionDate": "2023-10-02T10:00:00Z" } ] }
- GET
-
Refund Payment
- POST
/api/payments/refund
- Headers:
Authorization
:Bearer <admin_token>
// JWT token for admin authentication
- Request Body:
{ "paymentId": 1, "amount": 20.00 }
- Response:
{ "success": true, "message": "Payment refunded successfully", "data": { "refundId": 1, "paymentId": 1, "amount": 20.00, "status": "refunded" } }
- POST
-
Get Payment History
- GET
/api/payments/history
- Headers:
Authorization
:Bearer <admin_token>
// JWT token for admin authentication
- Response:
{ "success": true, "data": [ { "paymentId": 1, "reservationId": 1, "amount": 20.00, "status": "completed", "transactionDate": "2023-10-01T10:00:00Z" }, { "paymentId": 2, "reservationId": 2, "amount": 15.00, "status": "refunded", "transactionDate": "2023-10-02T10:00:00Z" } ] }
- GET
This project is licensed under the MIT License. See the LICENSE file for details.
This comprehensive API specification covers the essential functionalities of a Parking Reservation System, including Garage Management, Parking Spot Management, Parking Reservation, and Payment Processing. Each endpoint is meticulously detailed, specifying the required HTTP methods, request bodies, and expected response formats. This structure ensures clarity and precision, enabling developers to implement and utilize the API effectively for their applications. Adjust the specifics as necessary to align with your business logic and application requirements.