My Journey Debugging a Full-Stack Node.js Application: From Error Messages to Deployment
The Beginning: A TypeError That Started Everything
I had just finished building what I thought was a complete task management application with Node.js, Express, and MongoDB. The code looked clean, the structure made sense, and I was ready to test my creation. Then I ran npm start and was immediately greeted with:
TypeError: Cannot read properties of undefined (reading 'Store')
    at Object.<anonymous> (/home/lenovo/code/ltphongssvn/task-management-final/app.js:6:36)
This error taught me my first lesson: the order of require statements matters more than I initially realized. The issue was in these lines:
const MongoStore = require('connect-mongo')(session);
const session = require('express-session');
I was trying to use session before it was defined. The fix was simple once I understood the problem - just reorder the imports:
const session = require('express-session');
const MongoStore = require('connect-mongo');
The Second Challenge: Authentication Configuration
After fixing the import order, I encountered another error that seemed more cryptic:
Error: Unknown authentication strategy "local"
My investigation process began with checking what was actually happening in my authentication setup. I used cat config/passport.js to examine my Passport configuration file and discovered that while I had the configuration file, it wasn't being loaded anywhere in my application.
The solution required adding this line to my app.js after establishing the MongoDB connection:
require('./config/passport');
This experience taught me that having configuration files isn't enough - they need to be explicitly loaded into the application context.
The Third Hurdle: Template Variable References
Just when I thought I had everything working, visiting /tasks/new resulted in:
ReferenceError: /home/lenovo/code/ltphongssvn/task-management-final/views/tasks/new.ejs:142
    140|                 <label for="status">Status *</label>
    141|                 <select id="status" name="status" required>
 >> 142|                     <option value="pending" <%= (typeof formData !== 'undefined' && formData.status === 'pending') || !formData ? 'selected' : '' %>>Pending</option>
formData is not defined
I traced this issue by examining the controller with cat controllers/taskController.js | head -50. The problem was that my getNewTask function wasn't passing formData to the view:
// Before
exports.getNewTask = (req, res) => {
    res.render('tasks/new', {
        title: 'New Task - Task Management',
        _csrf: getToken(req, res)
    });
};
// After
exports.getNewTask = (req, res) => {
    res.render('tasks/new', {
        title: 'New Task - Task Management',
        _csrf: getToken(req, res),
        formData: {}  // Always provide formData, even if empty
    });
};
Investigation Techniques That Helped Me
Throughout this debugging journey, I developed a systematic approach:
- Read the error carefully: The line numbers and file paths in error messages were my roadmap
 - Use CLI tools for investigation: Commands like 
cat,head, andgrephelped me examine files without opening an editor - Check the flow: I traced how data moved from controllers to views to understand where variables were expected
 - Test incrementally: After each fix, I ran the application to ensure I hadn't introduced new issues
 
Deployment Challenges: The GitHub Security Alert
When I pushed my code to GitHub, I immediately received a security alert about exposed credentials in my .env.example file. The line causing the issue was:
# Atlas: mongodb+srv://username:[email protected]/database
GitHub's automated scanning couldn't distinguish between an example and real credentials. I learned that documentation needs to be written carefully to avoid false positives. The solution was to use placeholder syntax:
# MongoDB Atlas format: mongodb+srv://<username>:<password>@<cluster>.mongodb.net/<database>
This experience taught me that security scanners are aggressive by design, and I need to write examples that clearly look like placeholders, not potential credentials.
The Deployment Process
After resolving all local issues, I deployed to Render. Watching the deployment logs gave me insights into how production deployments work:
==> Running build command 'npm install'...
added 116 packages, and audited 117 packages in 2s
found 0 vulnerabilities
==> Build successful 🎉
==> Running 'npm start'
Server is running on port 10000
Successfully connected to MongoDB
Database: task-management
==> Your service is live 🎉
I noticed a warning about MemoryStore not being suitable for production, which reminded me that development conveniences don't always translate to production best practices.
Reflection on the Learning Process
This debugging journey transformed my understanding of full-stack development. Each error was a teacher, revealing assumptions I had made about how JavaScript modules load, how Express middleware chains work, and how template engines resolve variables.
What started as frustrating error messages became a masterclass in systematic debugging. I learned that most errors provide clear clues if I take time to read them carefully. The stack traces that initially seemed overwhelming became valuable maps showing exactly where problems occurred.
The most valuable lesson was developing patience with the debugging process. Instead of randomly changing code hoping something would work, I learned to investigate methodically, understand the root cause, and implement targeted fixes.
CLI Commands Reference for Future Debugging
Here are the commands that proved invaluable during my debugging session:
# View error details and file content
cat filename.js                    # Display entire file content
head -n 50 filename.js             # Show first 50 lines of a file
tail -n 20 filename.js             # Show last 20 lines of a file
grep -n "search_term" filename.js  # Search for specific terms with line numbers
# Git operations for tracking changes
git status                         # Check what files have changed
git diff filename.js               # See specific changes in a file
git log --oneline -n 5            # View recent commit history
# Process and port management
lsof -i :3000                      # Check what's using port 3000
ps aux | grep node                 # Find running Node processes
pkill -f nodemon                   # Kill all nodemon processes
# NPM and Node debugging
npm list package-name              # Check if a package is installed
npm run dev -- --inspect           # Run with Node debugger
node -e "console.log(require.resolve('package-name'))"  # Find where a package is installed
# File system navigation
ls -la                             # List all files with details
pwd                                # Show current directory path
find . -name "*.js" -type f       # Find all JavaScript files
# Environment and configuration
printenv | grep NODE               # Check Node-related environment variables
echo $NODE_ENV                     # Display specific environment variable
which node                         # Show Node.js installation path
These commands became my toolkit for understanding what was happening in my application at any given moment. They helped me move from confusion to clarity, from error messages to working solutions.
If you enjoyed this article, you can also find it published on LinkedIn and Medium.