Welcome to my programming challenge project! π
I created this project as the final task requested by Compass UOL for their scholarship program. The main goal of this project is to simulate a client request for a REST API in a leasing vehicles application. This includes handling authentication, security, validations, and CRUD operations for users, cars, and reserves.
Through this project, I have honed my skills in programming and web development, and I am excited to showcase my ability to create efficient and effective solutions to real-world problems.
Thank you for taking the time to check out my project!
- Readability
- Small commits
- README documentation
- Swagger documentation (Conflicts with deploy, so it need to be open locally)
- ESLint
- API deploy
- JWT
- Authentication route
- Protected routes (token bearer with 12h deadline)
- Unit tests (70% test coverage) - Achieved 60%
- The CEP must be sent as a request to an external API (viacep) in order to get the full adress.
- The user must be at least 18 years old.
- CPF must be unique in the base.
- Must have password with at least 6 digits.
- Must have a valid Email.
- The car's fabrication year must be between 1950 and 2023.
- Can't have repeated accessories.
- Cars must be listed with pagination and can be searched by query params (like model or color).
- Cars can be searched individually by id.
- User must have a driver's license in order to make a reservation.
- The same car cannot be reserved in the same period of time.
- The user cannot have more than one reservation in the same period of time.
- Start date cannot be greater than the end date.
You can test the API without running locally, using the application deployment using Postman or Insomnia..
You will need to have Node.JS installed on your machine, as well as a MongoDB Atlas database collection.
- Check if you have the listed pre-requisites.
- Clone the repository to your local machine with the command:
$ git clone https://github.com/flaviojrdev/grid-motors-api.git- Install project dependencies with the command:
$ npm install- Create a
.envfile at the project root (use the.env.examplefile as a template). - Start the server with the command:
$ npm run dev- The server will be running at
localhost:port.
- Make sure you have Docker and Docker Compose installed on your machine.
- Clone the repository to your local machine with the command:
$ git clone https://github.com/flaviojrdev/grid-motors-api.git- Navigate to the project root directory.
- Create a .env file at the project root (use the .env.example file as a template).
- Start the application using Docker Compose with the command:
docker-compose up -d- Docker Compose will download the necessary images and start the services defined in the docker-compose.yml file. The API will be accessible at http://localhost:, where is the port defined in the .env file.
- Node.JS: JavaScript development platform.
- Express: Web application framework.
- TypeScript: Typed superset of JavaScript.
- MongoDB: Document-oriented NoSQL database.
- Cors: Middleware to enable CORS (Cross-Origin Resource Sharing) on a Node.js server.
- Helmet: Middleware to improve Node.js security with HTTP headers.
- JsonWebToken: Library for generating JSON web tokens for authentication and authorization.
- Bcrypt: Hashing library for password encryption.
- Morgan: A logger middleware for Node.js that logs HTTP requests to the console or a file.
- Winston: Logging library for Node.js.
- Mongoose: MongoDB library with high-level API for defining and querying data models.
- Dotenv: Library to load env variables from .env file for easier environment configuration.
- Nodemon: Utility that monitors code changes and auto-restarts the server.
- Moment: Library for working with dates and times with easy parsing, manipulation, and display.
- Joi: Library for validating and sanitizing data with simple yet powerful API.
- Axios: Library for making HTTP requests with easy-to-use API for data transfer.
- Swagger: Tool for documenting and testing RESTful APIs with machine-readable format.
The project follows the SOLID principles. The src/api folder contains all the application's source code and is organized as follows:
- π entities: Contains the domain entities used by the application, such as Car, User and Reserve.
- π errors: Contains custom error classes used by the application.
- π interfaces: Contains interfaces used by the application.
- π middlewares: Contains middleware functions used by the application, such as authentication.
- π routes: Contains the API routes and their respective controllers.
- π useCases: Contains the business logic of the application, organized by domain entities.
- π utils: Contains utility functions used by the application, such as formatting and validations.
- π app.ts: Main file that configures the server, sets up middleware and connects to the database.
- π server.ts: Instantiates the server and listens for HTTP requests.
Aswell as a π data folder in src/data which contains the database class file.
- Main Route:
/api/v1
| Method | Endpoint | Protected | Description |
|---|---|---|---|
POST |
/auth | false | Authenticate a user |
Note This route is for generating or renewing the authentication token.
{
"email": "joazinho@email.com",
"password": "123456"
}{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0MzMxMjk2MDM5ZDk2MDg1NmQxZDZkYSIsImlhdCI6MTY4MTA2ODkwMiwiZXhwIjoxNjgxMTEyMTAyfQ.D5zEzXoHSvQZNKdW48Ip_HVq0jlVo9Wxuf_Rf4AhFdg"
}| Method | Endpoint | Protected | Description |
|---|---|---|---|
GET |
/user | true | List all users |
POST |
/user | false | Register a new user |
DELETE |
/user/:id | true | Remove a user by ID |
PUT |
/user/:id | true | Update a user by ID |
{
"name": "JoΓ£ozinho Ciclano",
"cpf": "131.147.860-49",
"birth": "03/03/2000",
"email": "joazinho@email.com",
"password": "123456",
"cep": "01001000",
"qualified": "yes"
}{
"_id": "64331296039d960856d1d6da",
"name": "JoΓ£ozinho Ciclano",
"cpf": "131.147.860-49",
"birth": "03/03/2000",
"email": "joazinho@email.com",
"password": "$2b$10$LatAA.HtTongQlvUbHBEkuEYmGOlzdH01WwHwzNscPxcUrwlrosIS",
"cep": "01001000",
"qualified": "yes",
"patio": "PraΓ§a da SΓ©",
"complement": "lado Γmpar",
"neighborhood": "SΓ©",
"locality": "SΓ£o Paulo",
"uf": "SP"
}{
"users": [
{
"_id": "64331296039d960856d1d6da",
"name": "JoΓ£ozinho Ciclano",
"cpf": "131.147.860-49",
"birth": "03/03/2000",
"email": "joazinho@email.com",
"password": "$2b$10$LatAA.HtTongQlvUbHBEkuEYmGOlzdH01WwHwzNscPxcUrwlrosIS",
"cep": "01001000",
"qualified": "yes",
"patio": "PraΓ§a da SΓ©",
"complement": "lado Γmpar",
"neighborhood": "SΓ©",
"locality": "SΓ£o Paulo",
"uf": "SP"
},
{
"_id": "64332347bf729514ccb4d316",
"name": "Julio Cesar",
"cpf": "390.442.050-05",
"birth": "03/03/2000",
"email": "Jcesar@email.com",
"password": "$2b$10$bB2PwqH55JHPA4UEAbsXPeu8c6XinCrgDeWMWK1ndcFWQwQF8HJd6",
"cep": "67020695",
"qualified": "no",
"patio": "Passagem SΓ£o Pedro",
"complement": "not provided",
"neighborhood": "Γguas Lindas",
"locality": "Ananindeua",
"uf": "PA"
},
{
"_id": "643323e36d85f20f99bbac18",
"name": "Bruce Wayne",
"cpf": "234.980.290-61",
"birth": "03/03/2000",
"email": "batman@email.com",
"password": "$2b$10$tBMtW1EIWmK/CLCnUSnKcetaFIjp9/fIdWPY8k8S8sn8/JbgVXT7C",
"cep": "58058260",
"qualified": "yes",
"patio": "Rua Sargento Adahylton Pontes de Lima",
"complement": "not provided",
"neighborhood": "Mangabeira",
"locality": "JoΓ£o Pessoa",
"uf": "PB"
}
],
"total": 3,
"limit": 100,
"offset": 1,
"offsets": 100
}{
"name": "JoΓ£ozinho Ciclano",
"cpf": "131.147.860-49",
"birth": "03/03/2000",
"email": "joazinho@email.com",
"password": "1234567",
"cep": "01001000",
"qualified": "yes",
"patio": "PraΓ§a da SΓ©",
"complement": "lado Γmpar",
"neighborhood": "SΓ©",
"locality": "SΓ£o Paulo",
"uf": "SP"
}{
"_id": "64331296039d960856d1d6da",
"name": "JoΓ£ozinho Ciclano",
"cpf": "131.147.860-49",
"birth": "03/03/2000",
"email": "joazinho@email.com",
"password": "$2b$10$o/GOIcUvTTwD33Mlc20tIeXPOc7jxkbGJqoqyKeDQ453z6pBpcX2W",
"cep": "01001000",
"qualified": "yes",
"patio": "PraΓ§a da SΓ©",
"complement": "lado Γmpar",
"neighborhood": "SΓ©",
"locality": "SΓ£o Paulo",
"uf": "SP"
}| Method | Endpoint | Protected | Description |
|---|---|---|---|
GET |
/car | true | List all cars |
GET |
/car/:id | true | List a car by ID |
POST |
/car | true | Register a new car |
DELETE |
/car/:id | true | Remove a car by ID |
PUT |
/car/:id | true | Update a car by ID |
PATCH |
/car/:carId/accessories/:accessoryId | true | Update or remove an accessory by ID |
Note If the accessory descriptions matches, it will be treated as a removal, otherwise its an update.
{
"model": "GM S10 2.8",
"color": "White",
"year": 2021,
"values_per_day": 50,
"accessories": [
{
"description": "air conditioner"
},
{
"description": "4x4 traction"
},
{
"description": "4 ports"
}
],
"number_of_passengers": 5
}{
"_id": "64331385039d960856d1d6df",
"model": "GM S10 2.8",
"color": "White",
"year": 2021,
"values_per_day": 50,
"accessories": [
{
"description": "air conditioner",
"_id": "64331385039d960856d1d6e0"
},
{
"description": "4x4 traction",
"_id": "64331385039d960856d1d6e1"
},
{
"description": "4 ports",
"_id": "64331385039d960856d1d6e2"
}
],
"number_of_passengers": 5
}{
"cars": [
{
"_id": "64331385039d960856d1d6df",
"model": "GM S10 2.8",
"color": "White",
"year": 2021,
"values_per_day": 50,
"accessories": [
{
"description": "air conditioner",
"_id": "64331385039d960856d1d6e0"
},
{
"description": "4x4 traction",
"_id": "64331385039d960856d1d6e1"
},
{
"description": "4 ports",
"_id": "64331385039d960856d1d6e2"
}
],
"number_of_passengers": 5
},
{
"_id": "643324406d85f20f99bbac23",
"model": "Ferrari",
"color": "Red",
"year": 2017,
"values_per_day": 1500,
"accessories": [
{
"description": "leather seats",
"_id": "643324406d85f20f99bbac24"
}
],
"number_of_passengers": 2
},
{
"_id": "6433249a6d85f20f99bbac27",
"model": "Batmobile",
"color": "Black",
"year": 1966,
"values_per_day": 200,
"accessories": [
{
"description": "rocket engine",
"_id": "6433249a6d85f20f99bbac28"
}
],
"number_of_passengers": 1
}
],
"total": 3,
"limit": 100,
"offset": 1,
"offsets": 100
}{
"model": "Ferrari",
"color": "Red",
"year": 2017,
"values_per_day": 2000,
"accessories": [
{
"description": "leather seats"
}
],
"number_of_passengers": 2
}{
"_id": "643324406d85f20f99bbac23",
"model": "Ferrari",
"color": "Red",
"year": 2017,
"values_per_day": 2000,
"accessories": [
{
"description": "leather seats",
"_id": "64338fc5db2e9b24b0e63d97"
}
],
"number_of_passengers": 2
}{
"description": "back camera"
}{
"_id": "64331385039d960856d1d6df",
"model": "GM S10 2.8",
"color": "White",
"year": 2021,
"values_per_day": 50,
"accessories": [
{
"description": "back camera",
"_id": "6433906adb2e9b24b0e63da6"
},
{
"description": "4x4 traction",
"_id": "64331385039d960856d1d6e1"
},
{
"description": "4 ports",
"_id": "64331385039d960856d1d6e2"
}
],
"number_of_passengers": 5
}Note Deletion returns an empty response with status 204.
{
"description": "back camera"
}{
"message": "Accessory deleted",
"car": {
"_id": "64331385039d960856d1d6df",
"model": "GM S10 2.8",
"color": "White",
"year": 2021,
"values_per_day": 50,
"accessories": [
{
"description": "4x4 traction",
"_id": "64331385039d960856d1d6e1"
},
{
"description": "4 ports",
"_id": "64331385039d960856d1d6e2"
}
],
"number_of_passengers": 5
}
}| Method | Endpoint | Protected | Description |
|---|---|---|---|
POST |
/reserve | true | Register a new reservation |
GET |
/reserve | true | List all reservations |
DELETE |
/reserve/:id | true | Remove a reservation by ID |
PUT |
/reserve/:id | true | Update a reservation by ID |
{
"start_date": "20/05/2023",
"end_date": "30/05/2023",
"id_car": "643324406d85f20f99bbac23"
}{
"_id": "64332be36d85f20f99bbac6a",
"start_date": "20/05/2023",
"end_date": "30/05/2023",
"final_value": 15000,
"id_car": "643324406d85f20f99bbac23",
"id_user": "643323e36d85f20f99bbac18"
}{
"reserves": [
{
"_id": "643326f16d85f20f99bbac4e",
"start_date": "20/04/2023",
"end_date": "30/04/2023",
"final_value": 500,
"id_car": "64331385039d960856d1d6df",
"id_user": "64331296039d960856d1d6da"
},
{
"_id": "64332bc26d85f20f99bbac63",
"start_date": "10/05/2023",
"end_date": "15/05/2023",
"final_value": 1000,
"id_car": "6433249a6d85f20f99bbac27",
"id_user": "643323e36d85f20f99bbac18"
},
{
"_id": "64332be36d85f20f99bbac6a",
"start_date": "20/05/2023",
"end_date": "30/05/2023",
"final_value": 15000,
"id_car": "643324406d85f20f99bbac23",
"id_user": "643323e36d85f20f99bbac18"
}
],
"total": 3,
"limit": 100,
"offset": 1,
"offsets": 100
}{
"start_date": "21/04/2023",
"end_date": "30/04/2023",
"final_value": 1000,
"id_car": "64331385039d960856d1d6df",
"id_user": "64331296039d960856d1d6da"
}{
"_id": "643326f16d85f20f99bbac4e",
"start_date": "21/04/2023",
"end_date": "30/04/2023",
"final_value": 1000,
"id_car": "64331385039d960856d1d6df",
"id_user": "64331296039d960856d1d6da"
}| Field | Type | Required | Unique |
|---|---|---|---|
| name | String | Yes | No |
| cpf | String | Yes | Yes |
| birth | String | Yes | No |
| String | Yes | Yes | |
| password | String | Yes | No |
| cep | String | Yes | No |
| qualified | String | Yes | No |
| patio | String | Yes | No |
| complement | String | Yes | No |
| neighborhood | String | Yes | No |
| locality | String | Yes | No |
| uf | String | Yes | No |
Note
patio,complement,neighborhood,localityandufare not passed in the POST request (only in PUT requests). They are filled in the payload via a external API requisition using thecep.
| Field | Type | Required | Unique |
|---|---|---|---|
| description | String | Yes | No |
| Field | Type | Required | Unique |
|---|---|---|---|
| model | String | Yes | No |
| color | String | Yes | No |
| year | Number | Yes | No |
| values_per_day | Number | Yes | No |
| accessories | [AccessorySchema] | Yes | No |
| number_of_passengers | Number | Yes | No |
| Field | Type | Required | Unique |
|---|---|---|---|
| start_date | String | Yes | No |
| end_date | String | Yes | No |
| final_value | Number | Yes | No |
| id_car | ObjectId (Car) | Yes | No |
| id_user | ObjectId (User) | Yes | No |
| Flavio Henrique |

