Episode 3 — NodeJS MongoDB Backend Architecture / 3.13 — Production Project Structure
3.13 — Exercise Questions: Production Project Structure
Practice for Section 3.13 -- project structure, file naming, Git configuration, environment variables, PM2, error/response handling, CORS, ESLint/Prettier, and Postman testing.
How to use this material (instructions)
- Read
README.md->3.13.athrough3.13.f. - Answer closed-book -- then compare to lessons.
- Interview prep --
3.13-Interview-Questions.md. - Cheat sheet --
3.13-Quick-Revision.md.
Project Structure & Separation of Concerns (Q1--Q8)
Q1. Name the two dominant strategies for organising backend code. Which is better for a project with 15+ resources?
Q2. In a standard Express project, list all the folders that typically live inside src/.
Q3. Explain why app.js and server.js are kept as separate files. Give two concrete benefits.
Q4. What is the single responsibility of each layer: route, controller, service, model?
Q5. A teammate writes database queries directly inside a controller function. Explain why this violates separation of concerns and what they should do instead.
Q6. What is the purpose of a routes/index.js file, and how does it simplify app.js?
Q7. Draw the request flow from client to database and back, naming every layer the request passes through.
Q8. Your project has utils/, middleware/, and validators/ folders. A teammate asks: "Why not merge them into one helpers/ folder?" Give three reasons to keep them separate.
File Naming Conventions (Q9--Q14)
Q9. What is the recommended file naming convention for Node.js projects, and why is it safer than PascalCase on Linux servers?
Q10. Write the correct file name for: a user controller, a post model, an auth middleware, and a user validator.
Q11. What is the case-sensitivity trap, and how does it cause "module not found" errors in production?
Q12. A developer names their files UserService.js, userRoute.js, and post-model.js in the same project. What is wrong with this approach?
Q13. What naming pattern signals a file's role at a glance without opening it? Give the general format.
Q14. Why do some teams use PascalCase specifically for model files (e.g., User.js instead of user.model.js)? What is the trade-off?
Git Configuration (Q15--Q21)
Q15. Write a .gitignore entry that keeps the public/uploads/ directory structure but ignores all files inside it. Explain the .gitkeep trick.
Q16. List five categories of files that should always appear in a Node.js project's .gitignore.
Q17. Rewrite the following bad commit message using Conventional Commits format: "fixed login stuff and updated readme".
Q18. What branch prefix would you use for each: a new search feature, an urgent production bug, a documentation update?
Q19. What does .editorconfig do, and why is it needed when the team already uses Prettier?
Q20. Describe what Husky is and list three Git hooks it can automate. What happens if a pre-commit hook exits with a non-zero code?
Q21. What does commitlint do, and how does it integrate with Husky?
Environment Variables & .env Management (Q22--Q28)
Q22. Why should you never hardcode a database URL, JWT secret, or port number in source code?
Q23. What does the dotenv package do? On which line of which file should require('dotenv').config() be called, and why?
Q24. What is the difference between .env and .env.example? Which one is committed to the repository?
Q25. A teammate accidentally committed .env to GitHub with the production database password. List every step you would take to fix this.
Q26. What is NODE_ENV and how does it typically change application behaviour across development, production, and test?
Q27. Explain the "config module pattern" (src/config/index.js). Why is it better than scattering process.env.SOMETHING across the codebase?
Q28. What does "fail fast" mean for environment variable validation? Write a snippet that crashes the app immediately if MONGODB_URI and JWT_SECRET are missing.
PM2 Process Management (Q29--Q33)
Q29. List five problems PM2 solves that node server.js does not.
Q30. Explain PM2 cluster mode. How many instances does pm2 start server.js -i max create, and how does load balancing work?
Q31. Write a minimal ecosystem.config.js that runs your app in cluster mode with all CPUs, restarts on crash, and sets NODE_ENV to production.
Q32. What two commands must you run (once) so that PM2 and your app auto-start after a server reboot?
Q33. What is the difference between pm2 restart and pm2 reload? When would you use each?
Error Handling & Response Classes (Q34--Q39)
Q34. Write the complete ApiError class from memory. Include statusCode, message, errors, isOperational, and Error.captureStackTrace.
Q35. What is the difference between an operational error and a programming error? Give an example of each. Which type should show its real message to the client?
Q36. Write the asyncHandler utility function from memory. Explain what Promise.resolve().catch() does in this context.
Q37. Without asyncHandler, what happens if an async controller throws and there is no try/catch? What error does the client see?
Q38. Write the ApiResponse class. Then show how a controller uses it to send a 201 response for a newly created post.
Q39. In the global error handler middleware, why must you handle Mongoose CastError, code 11000, and ValidationError specifically? Write the handler for one of these.
CORS Configuration (Q40--Q43)
Q40. What does CORS protect against? Why is app.use(cors()) acceptable in development but dangerous in production?
Q41. Write a CORS configuration object that whitelists two origins from an environment variable, allows credentials, and caches preflight for 24 hours.
Q42. A request from a mobile app has origin: undefined. Should your CORS config reject it? Why or why not?
Q43. What is a preflight request? Which HTTP method does it use, and what does the maxAge option control?
ESLint & Prettier Setup (Q44--Q50)
Q44. What is the fundamental difference between what ESLint catches and what Prettier fixes? Give two examples of each.
Q45. Why does eslint-config-prettier need to be the last entry in the ESLint extends array?
Q46. Write the npm install command to set up ESLint + Prettier with their integration packages.
Q47. What does lint-staged do, and why is it faster than running eslint . on every commit?
Q48. Describe the full developer pipeline from writing code to a successful commit, naming every tool that runs at each stage.
Q49. Write a .prettierrc configuration that uses single quotes, 2-space indentation, trailing commas everywhere, semicolons, and 100-character line width.
Q50. What goes in .eslintignore and .prettierignore? Name four entries common to both.
Postman Testing (Q51--Q56)
Q51. What is a Postman collection, and how should you organise requests within it for a blog API?
Q52. Explain Postman environment variables. Why should you use {{baseUrl}} instead of hardcoding http://localhost:3000?
Q53. Write a Postman Tests tab script that: (a) checks the status is 200, (b) verifies response.data has an id property, and (c) saves that id to the environment.
Q54. What is the Collection Runner, and how does data-driven testing with a CSV file work?
Q55. What is Newman? Write the command to run a Postman collection against a local environment file with an HTML report.
Q56. Describe how Newman fits into a GitHub Actions CI/CD pipeline. What files are needed and what happens on test failure?
Answer hints
| Q | Hint |
|---|---|
| Q1 | Feature-based is better for 15+ resources; each feature in its own folder |
| Q3 | Testability (import app without binding a port) and serverless portability |
| Q5 | Move DB queries to the service layer; controller calls the service |
| Q9 | kebab-case -- avoids case-sensitivity issues between macOS/Windows and Linux |
| Q11 | macOS is case-insensitive; Linux is case-sensitive -- User.js vs user.js are the same locally but different on the server |
| Q15 | public/uploads/* + !public/uploads/.gitkeep |
| Q17 | fix(auth): correct login error handling and docs(readme): update setup instructions -- two separate commits |
| Q22 | Secrets in source code are visible to anyone with repo access; env vars keep them out of version control |
| Q24 | .env.example is committed (placeholder values); .env is gitignored (real secrets) |
| Q25 | git rm --cached .env, add to .gitignore, commit, then rotate all exposed secrets immediately |
| Q27 | Single source of truth, type casting, default values, easy to mock in tests |
| Q28 | Filter requiredEnvVars against process.env, throw if any are missing |
| Q30 | One instance per CPU core; PM2 uses round-robin load balancing across workers |
| Q33 | restart = kill + start (brief downtime); reload = zero-downtime graceful restart |
| Q35 | Operational = expected (404, duplicate email); programming = bug (TypeError). Only operational errors show real messages |
| Q36 | Promise.resolve() wraps the async return; .catch(next) forwards rejections to Express error handler |
| Q37 | Unhandled promise rejection; the client may hang or receive no response |
| Q40 | cors() with no options allows all origins -- any website can call your API |
| Q42 | Allow it -- mobile apps and server-to-server requests have no origin header |
| Q45 | It disables conflicting ESLint formatting rules; if it is not last, another preset could re-enable them |
| Q47 | lint-staged only lints files that are git added, not the entire codebase |
| Q55 | newman run Collection.json --environment Env.json --reporters cli,htmlextra --reporter-htmlextra-export report.html |
<- Back to 3.13 -- README