├── README.md └── sample-data.json /README.md: -------------------------------------------------------------------------------- 1 | # Mongoose Express CRUD Mastery 2 | **Objective:** Develop a Node.js Express application with TypeScript as the programming language, integrating MongoDB with Mongoose for user data and order management. Ensure data integrity through validation using Joi/Zod. 3 | 4 | ### Set up the Project 5 | 6 | - Create a new Node.js Express project. 7 | - Set up a MongoDB database using Mongoose for storing user and order data. 8 | 9 | ### Define Data Models 10 | 11 | - Create Mongoose models for User data based on the provided data structure. (You can follow sample-data.json file for ideas) 12 | - Define appropriate data types, validations. 13 | 14 | ### Data Types List 15 | 16 | - `userId` (number): A unique identifier for the user. 17 | - `username` (string): Denotes the user's unique username, ensuring uniqueness across the system. 18 | - `password` (string): Represents the user's password. The password is securely stored in hashed form, utilizing the bcrypt algorithm for hashing. 19 | - `fullName` (object): An object containing the first and last name of the user. 20 | - `firstName` (string): The first name of the user. 21 | - `lastName` (string): The last name of the user. 22 | - `age` (number): The age of the user. 23 | - `email` (string): The email address of the user. 24 | - `isActive` (boolean): A flag indicating whether the user is active or not. 25 | - `hobbies` (array of strings): An array containing the hobbies of the user. 26 | - `address` (object): An object containing the street, city, and country of the user's address. 27 | - `street` (string): The street of the user's address. 28 | - `city` (string): The city of the user's address. 29 | - `country` (string): The country of the user's address. 30 | - `orders` (array of objects): An array containing the orders of the user. 31 | - `productName` (string): The name of the product in the order. 32 | - `price` (number): The price of the product in the order. 33 | - `quantity` (number): The quantity of the product in the order. 34 | 35 | 36 | ## Main Section (50 Marks): 37 | 38 | ### User Management: 39 | 40 | ### 1. Create a new user 41 | 42 | - Endpoint: **POST /api/users** 43 | - Request Body: 44 | 45 | ```json 46 | { 47 | "userId": "number", 48 | "username": "string", 49 | "password": "string", 50 | "fullName": { 51 | "firstName": "string", 52 | "lastName": "string" 53 | }, 54 | "age": "number", 55 | "email": "string", 56 | "isActive": "boolean", 57 | "hobbies": [ 58 | "string", 59 | "string" 60 | ], 61 | "address": { 62 | "street": "string", 63 | "city": "string", 64 | "country": "string" 65 | } 66 | } 67 | ``` 68 | 69 | - Response: Newly created user object. **Make sure that the password field is not included in the response data.** 70 | 71 | ```json 72 | { 73 | "success": true, 74 | "message": "User created successfully!", 75 | "data": { 76 | "userId": "number", 77 | "username": "string", 78 | "fullName": { 79 | "firstName": "string", 80 | "lastName": "string" 81 | }, 82 | "age": "number", 83 | "email": "string", 84 | "isActive": "boolean", 85 | "hobbies": [ 86 | "string", 87 | "string" 88 | ], 89 | "address": { 90 | "street": "string", 91 | "city": "string", 92 | "country": "string" 93 | } 94 | } 95 | } 96 | ``` 97 | 98 | ### 2. Retrieve a list of all users 99 | 100 | - Endpoint: **GET /api/users** 101 | - Response: List of user objects. Each object should only contain `username`, `fullName`, `age`, `email`, `address` . Apply suitable field filtering to exclusively retrieve the necessary information. 102 | 103 | ```json 104 | { 105 | "success": true, 106 | "message": "Users fetched successfully!", 107 | "data": [ 108 | { 109 | "username": "string", 110 | "fullName": { 111 | "firstName": "string", 112 | "lastName": "string" 113 | }, 114 | "age": "number", 115 | "email": "string", 116 | "address": { 117 | "street": "string", 118 | "city": "string", 119 | "country": "string" 120 | } 121 | }, 122 | // more objects... 123 | ] 124 | } 125 | ``` 126 | 127 | ### 3. Retrieve a specific user by ID 128 | 129 | - Endpoint: **GET /api/users/:userId** 130 | 131 | - Response: User object and make sure that the password field is not included in the response data. If you can't find information about the user, show a clear message. Use either `instance` or `static` method to determine if the user exist or not. (Use the [format for error messages](#sample-error-response) that is given below.) 132 | 133 | ```json 134 | { 135 | "success": true, 136 | "message": "User fetched successfully!", 137 | "data": { 138 | "userId": "number", 139 | "username": "string", 140 | "fullName": { 141 | "firstName": "string", 142 | "lastName": "string" 143 | }, 144 | "age": "number", 145 | "email": "string", 146 | "isActive": "boolean", 147 | "hobbies": [ 148 | "string", 149 | "string" 150 | ], 151 | "address": { 152 | "street": "string", 153 | "city": "string", 154 | "country": "string" 155 | } 156 | } 157 | } 158 | ``` 159 | 160 | ### 4. Update user information 161 | 162 | - Endpoint: **PUT /api/users/:userId** 163 | 164 | - Request Body: Updated user data (similar structure as in user creation). 165 | 166 | - Response: Updated user object and make sure that the password field is not included in the response data. If you can't find information about the user, show a clear message. Use either `instance` or `static` method to determine if the user exist or not. (Use the [format for error messages](#sample-error-response) that is given below.) 167 | 168 | ```json 169 | { 170 | "success": true, 171 | "message": "User updated successfully!", 172 | "data": { 173 | "userId": "number", 174 | "username": "string", 175 | "fullName": { 176 | "firstName": "string", 177 | "lastName": "string" 178 | }, 179 | "age": "number", 180 | "email": "string", 181 | "isActive": "boolean", 182 | "hobbies": [ 183 | "string", 184 | "string" 185 | ], 186 | "address": { 187 | "street": "string", 188 | "city": "string", 189 | "country": "string" 190 | } 191 | } 192 | } 193 | ``` 194 | 195 | ### 5. Delete a user 196 | 197 | - Endpoint: **DELETE /api/users/:userId** 198 | 199 | - Response: Success message or, If you can't find information about the user, show a clear message. Use either `instance` or `static` method to determine if the user exist or not. (Use the [format for error messages](#sample-error-response) that is given below.). 200 | 201 | ```json 202 | { 203 | "success": true, 204 | "message": "User deleted successfully!", 205 | "data" : null 206 | } 207 | ``` 208 | 209 | ## Bonus Section (10 marks): 210 | 211 | ### Order Management: 212 | 213 | 1. Add New Product in Order 214 | 215 | If the 'orders' property already exists for a user, append a new product to it. Otherwise, create an 'orders' array within the user object and then add the order data. 216 | 217 | - Endpoint: **PUT /api/users/:userId/orders** 218 | 219 | - Request Body: If you can't find information about the user, show a clear message. Use either `instanceof` or `static` method to display this error message. (Use the [format for error messages](#sample-error-response) that is given below.) 220 | 221 | ```json 222 | { 223 | "productName": "string", 224 | "price": "number", 225 | "quantity": "number" 226 | } 227 | ``` 228 | 229 | - Response: 230 | 231 | ```json 232 | { 233 | "success": true, 234 | "message": "Order created successfully!", 235 | "data": null 236 | } 237 | ``` 238 | 239 | ### 2. Retrieve all orders for a specific user 240 | 241 | - Endpoint: **GET /api/users/:userId/orders** 242 | 243 | - Response: List of order objects for the specified user or, If you can't find information about the user, show a clear message. Use either `instance` or `static` method to determine if the user exist or not. (Use the [format for error messages](#sample-error-response) that is given below.) 244 | 245 | 246 | ```json 247 | { 248 | "success": true, 249 | "message": "Order fetched successfully!", 250 | "data": { 251 | "orders": [ 252 | { 253 | "productName": "Product 1", 254 | "price": 23.56, 255 | "quantity": 2 256 | }, 257 | { 258 | "productName": "Product 2", 259 | "price": 23.56, 260 | "quantity": 5 261 | } 262 | ] 263 | } 264 | } 265 | ``` 266 | 267 | ### 3. **Calculate Total Price of Orders for a Specific User** 268 | 269 | - Endpoint: **GET /api/users/:userId/orders/total-price** 270 | - Response: Total price of all orders for the specified user or, If you can't find information about the user, show a clear message. Use either `instance` or `static` method to determine if the user exist or not (Use the [format for error messages](#sample-error-response) that is given below.) 271 | 272 | ```json 273 | { 274 | "success": true, 275 | "message": "Total price calculated successfully!", 276 | "data": { 277 | "totalPrice": 454.32 278 | } 279 | } 280 | ``` 281 | 282 | ### Sample Error Response 283 | 284 | ```json 285 | { 286 | "success": false, 287 | "message": "User not found", 288 | "error": { 289 | "code": 404, 290 | "description": "User not found!" 291 | } 292 | } 293 | ``` 294 | 295 | ## Validation with Joi/Zod 296 | 297 | - Use Joi/zod to validate incoming data for user and order creation and updating operations. 298 | - Ensure that the data adheres to the structure defined in the models. 299 | - Handle validation errors gracefully and provide meaningful error messages in the API responses. 300 | 301 | ## Instruction 302 | 303 | 1. **Coding Quality:** 304 | - Write clean, modular, and well-organized code. 305 | - Follow consistent naming conventions for variables, functions, and routes. 306 | - Use meaningful names that reflect the purpose of variables and functions. 307 | - Ensure that the code is readable. 308 | 2. **Comments:** 309 | - Try to provide inline comments to explain complex sections of code or logic. 310 | 3. **API Endpoint Adherence:** 311 | - Strictly follow the provided API endpoint structure and naming conventions. 312 | - Ensure that the request and response formats match the specifications outlined in the assignment. 313 | 4. **Validation and Error Handling:** 314 | - Implement validation using Joi/zod for both user and order data. 315 | - Handle validation errors gracefully and provide meaningful error messages in the API responses. 316 | - Implement error handling for scenarios like user not found, validation errors. 317 | 5. **Coding Tools and Libraries:** 318 | - Avoid the use of AI tools or libraries for generating code. Write the code manually to demonstrate a clear understanding of the concepts. 319 | - Utilize only the specified libraries like Express, Mongoose, Joi and avoid unnecessary dependencies. 320 | 6. **Coding Style:** 321 | - Consider using linting tools (e.g., ESLint) to enforce coding style and identify potential issues. 322 | - Ensure there are at least 10 commits in your GitHub repository. 323 | 324 | ***Not following the specified API endpoint structure, naming conventions, and other instructions will result in a deduction of marks.*** 325 | 326 | ### **Submission:** 327 | 328 | - Share the GitHub repository link and the live deployment link as part of your submission. 329 | - Include a README file with clear instructions on how to run the application locally. 330 | 331 | ### **Deadline:** 332 | 333 | - 60 marks: November 24, 2023, 11:59 PM 334 | - 50 marks: November 25, 2023, 11:59 PM 335 | - 30 marks: After 25, November, 11.59PM -------------------------------------------------------------------------------- /sample-data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "userId": 1, 4 | "username": "john_doe", 5 | "password": "$2b$12$3fJyHTgM8QgU.q.tlpNVyOf.hJYfhVe7XPGCHm9Wq1RmexUZbUEeu", 6 | "fullName": { 7 | "firstName": "John", 8 | "lastName": "Doe" 9 | }, 10 | "age": 30, 11 | "email": "john.doe@example.com", 12 | "isActive": true, 13 | "hobbies": [ 14 | "reading", 15 | "traveling" 16 | ], 17 | "address": { 18 | "street": "123 Main St", 19 | "city": "Anytown", 20 | "country": "USA" 21 | }, 22 | "orders": [ 23 | { 24 | "productName": "Product 1", 25 | "price": 23.56, 26 | "quantity": 2 27 | }, 28 | { 29 | "productName": "Product 2", 30 | "price": 23.56, 31 | "quantity": 5 32 | } 33 | ] 34 | }, 35 | { 36 | "userId": 2, 37 | "username": "jane_smith", 38 | "password": "$2b$12$3fJyHTgM8QgU.q.tlpNVyOf.hJYfhVe7XPGCHm9Wq1RmexUZbUEeu", 39 | "fullName": { 40 | "firstName": "Jane", 41 | "lastName": "Smith" 42 | }, 43 | "age": 25, 44 | "email": "jane.smith@example.com", 45 | "isActive": true, 46 | "hobbies": [ 47 | "photography", 48 | "gardening" 49 | ], 50 | "address": { 51 | "street": "456 Oak Ave", 52 | "city": "Smalltown", 53 | "country": "USA" 54 | }, 55 | "orders": [ 56 | { 57 | "productName": "Product 3", 58 | "price": 45.99, 59 | "quantity": 3 60 | } 61 | ] 62 | }, 63 | { 64 | "userId": 3, 65 | "username": "alice_davis", 66 | "password": "$2b$12$3fJyHTgM8QgU.q.tlpNVyOf.hJYfhVe7XPGCHm9Wq1RmexUZbUEeu", 67 | "fullName": { 68 | "firstName": "Alice", 69 | "lastName": "Davis" 70 | }, 71 | "age": 28, 72 | "email": "alice.davis@example.com", 73 | "isActive": true, 74 | "hobbies": [ 75 | "painting", 76 | "swimming" 77 | ], 78 | "address": { 79 | "street": "789 Pine St", 80 | "city": "Largetown", 81 | "country": "USA" 82 | }, 83 | "orders": [] 84 | }, 85 | { 86 | "userId": 4, 87 | "username": "mark_johnson", 88 | "password": "$2b$12$3fJyHTgM8QgU.q.tlpNVyOf.hJYfhVe7XPGCHm9Wq1RmexUZbUEeu", 89 | "fullName": { 90 | "firstName": "Mark", 91 | "lastName": "Johnson" 92 | }, 93 | "age": 35, 94 | "email": "mark.johnson@example.com", 95 | "isActive": true, 96 | "hobbies": [ 97 | "cooking", 98 | "hiking" 99 | ], 100 | "address": { 101 | "street": "789 Elm St", 102 | "city": "Hometown", 103 | "country": "USA" 104 | }, 105 | "orders": [ 106 | { 107 | "productName": "Product 7", 108 | "price": 12.99, 109 | "quantity": 4 110 | }, 111 | { 112 | "productName": "Product 8", 113 | "price": 19.99, 114 | "quantity": 2 115 | } 116 | ] 117 | }, 118 | { 119 | "userId": 5, 120 | "username": "sarah_brown", 121 | "password": "$2b$12$3fJyHTgM8QgU.q.tlpNVyOf.hJYfhVe7XPGCHm9Wq1RmexUZbUEeu", 122 | "fullName": { 123 | "firstName": "Sarah", 124 | "lastName": "Brown" 125 | }, 126 | "age": 42, 127 | "email": "sarah.brown@example.com", 128 | "isActive": true, 129 | "hobbies": [ 130 | "painting" 131 | ], 132 | "address": { 133 | "street": "987 Oak St", 134 | "city": "Cityville", 135 | "country": "USA" 136 | }, 137 | "orders": [ 138 | { 139 | "productName": "Product 10", 140 | "price": 39.99, 141 | "quantity": 3 142 | } 143 | ] 144 | }, 145 | { 146 | "userId": 6, 147 | "username": "new_user", 148 | "password": "$2b$12$3fJyHTgM8QgU.q.tlpNVyOf.hJYfhVe7XPGCHm9Wq1RmexUZbUEeu", 149 | "fullName": { 150 | "firstName": "New", 151 | "lastName": "User" 152 | }, 153 | "age": 20, 154 | "email": "new.user@example.com", 155 | "isActive": true, 156 | "hobbies": [ 157 | "reading", 158 | "writing" 159 | ], 160 | "address": { 161 | "street": "789 Elm St", 162 | "city": "Newtown", 163 | "country": "USA" 164 | }, 165 | "orders": [] 166 | }, 167 | { 168 | "userId": 7, 169 | "username": "james_smith", 170 | "password": "$2b$12$3fJyHTgM8QgU.q.tlpNVyOf.hJYfhVe7XPGCHm9Wq1RmexUZbUEeu", 171 | "fullName": { 172 | "firstName": "James", 173 | "lastName": "Smith" 174 | }, 175 | "age": 32, 176 | "email": "james.smith@example.com", 177 | "isActive": true, 178 | "hobbies": [ 179 | "photography", 180 | "cooking" 181 | ], 182 | "address": { 183 | "street": "456 Oak Ave", 184 | "city": "Bigtown", 185 | "country": "USA" 186 | }, 187 | "orders": [ 188 | { 189 | "productName": "Product 13", 190 | "price": 10.99, 191 | "quantity": 3 192 | }, 193 | { 194 | "productName": "Product 14", 195 | "price": 25.50, 196 | "quantity": 1 197 | } 198 | ] 199 | }, 200 | { 201 | "userId": 8, 202 | "username": "emily_davis", 203 | "password": "$2b$12$3fJyHTgM8QgU.q.tlpNVyOf.hJYfhVe7XPGCHm9Wq1RmexUZbUEeu", 204 | "fullName": { 205 | "firstName": "Emily", 206 | "lastName": "Davis" 207 | }, 208 | "age": 28, 209 | "email": "emily.davis@example.com", 210 | "isActive": true, 211 | "hobbies": [ 212 | "painting", 213 | "swimming", 214 | "cooking" 215 | ], 216 | "address": { 217 | "street": "789 Pine St", 218 | "city": "Largetown", 219 | "country": "USA" 220 | }, 221 | "orders": [ 222 | { 223 | "productName": "Product 15", 224 | "price": 19.99, 225 | "quantity": 2 226 | }, 227 | { 228 | "productName": "Product 16", 229 | "price": 12.50, 230 | "quantity": 3 231 | } 232 | ] 233 | }, 234 | { 235 | "userId": 9, 236 | "username": "michael_johnson", 237 | "password": "$2b$12$3fJyHTgM8QgU.q.tlpNVyOf.hJYfhVe7XPGCHm9Wq1RmexUZbUEeu", 238 | "fullName": { 239 | "firstName": "Michael", 240 | "lastName": "Johnson" 241 | }, 242 | "age": 35, 243 | "email": "michael.johnson@example.com", 244 | "isActive": true, 245 | "hobbies": [ 246 | "cooking", 247 | "hiking" 248 | ], 249 | "address": { 250 | "street": "789 Elm St", 251 | "city": "Hometown", 252 | "country": "USA" 253 | }, 254 | "orders": [ 255 | { 256 | "productName": "Product 17", 257 | "price": 14.99, 258 | "quantity": 4 259 | }, 260 | { 261 | "productName": "Product 18", 262 | "price": 9.99, 263 | "quantity": 2 264 | } 265 | ] 266 | }, 267 | { 268 | "userId": 10, 269 | "username": "linda_brown", 270 | "password": "$2b$12$3fJyHTgM8QgU.q.tlpNVyOf.hJYfhVe7XPGCHm9Wq1RmexUZbUEeu", 271 | "fullName": { 272 | "firstName": "Linda", 273 | "lastName": "Brown" 274 | }, 275 | "age": 42, 276 | "email": "linda.brown@example.com", 277 | "isActive": true, 278 | "hobbies": [ 279 | "yoga", 280 | "painting" 281 | ], 282 | "address": { 283 | "street": "987 Oak St", 284 | "city": "Cityville", 285 | "country": "USA" 286 | }, 287 | "orders": [ 288 | { 289 | "productName": "Product 19", 290 | "price": 8.99, 291 | "quantity": 1 292 | }, 293 | { 294 | "productName": "Product 20", 295 | "price": 19.99, 296 | "quantity": 3 297 | }, 298 | { 299 | "productName": "Product 21", 300 | "price": 190.99, 301 | "quantity": 31 302 | } 303 | ] 304 | } 305 | ] --------------------------------------------------------------------------------