Structure & Code Quality
- Separate app.js (routes/middleware) from server.js (the actual .listen() call) for testability
- Group routes by resource using express.Router()
- Use async/await with a consistent error-handling pattern (a wrapper or try/catch + next(err))
- Validate input at the boundary — reject bad requests before they reach business logic
Security
- Never commit secrets — use environment variables and .gitignore .env
- Hash passwords with bcrypt or argon2, never store them in plain text
- Use parameterized queries for SQL; never concatenate user input into a query
- Enable helmet() and a properly scoped CORS policy
- Validate and limit file upload size and type
Graceful Shutdown
Try it yourself — edit and run
Console Output
Click “Run” to see the console output here.
Reliability
| Practice | Why |
|---|---|
| Centralize error handling | Consistent responses, easier debugging, no duplicated logic |
| Log structured errors, not just console.log | Machine-readable logs are searchable in production |
| Handle graceful shutdown | Finish in-flight requests before a container is killed |
| Set request timeouts | A hung upstream dependency shouldn't hang your whole server |
Don't Ignore Unhandled Rejections
An unhandled promise rejection can silently fail or, in newer Node.js versions, crash the process entirely. Always add a process.on('unhandledRejection', ...) handler during development to catch bugs where a Promise's error was never caught.
Best Practice
Treat this checklist as a starting point, not a final answer — the right Node.js practices depend heavily on your app's scale and requirements. What matters most is being deliberate about these decisions rather than accepting whatever a tutorial's defaults happened to be.