How Do I Fix an “Undefined” Error Despite Linking HTML Correctly
When I first ran into the dreaded agentData is not defined error, I was confused. My HTML and EJS files were linked correctly, my routes were working, and I was sure I had declared agentData in my code. Yet every time I opened the /admin page, my app crashed.
What Actually Causing agentData is not defined?
The problem wasn’t with HTML or EJS at all — it was with how I structured my routes.
I realized I had two /admin routes:
- One in
routes.jsthat did this:
res.render('adminhome', { agentData: data })
- Another one (in
app.jsor elsewhere) that just did:
res.render('adminhome')
When Express hit the second version, the one that didn’t pass agentData, my EJS tried to run:
if (agentData.length != 0) {
And of course, since agentData wasn’t passed, the template crashed with an “undefined” error.
Why Removing the /admin Route Gave Me “Cannot GET /admin”
When I deleted the “good” /admin route from routes.js, only the “bad” one (or none at all) was left.
That meant Express didn’t know how to fetch my data and render the view, so it gave me:
Cannot GET /admin
This was a good clue that my routes were overlapping and inconsistent.
Another Fragile Point EJS Checks
Even if I fixed the routes, I still had this in my EJS:
if (agentData.length != 0) {
If agentData was ever missing — maybe because of a database error — that line would throw an exception before the else could even run. The fix? Use safer checks like:
<% const list = Array.isArray(agentData) ? agentData : []; %>
<% if (list.length) { %>
...
<% } else { %>
<tr><td colspan="5">No Data Found</td></tr>
<% } %>
That way, even if agentData wasn’t defined properly, my template wouldn’t explode.
The Fix
Here’s the approach I took:
- Have exactly one
/adminroute inroutes.jsthat fetches from MySQL and renders the page withagentData. - Mount my router once in
app.jsusingapp.use('/', routes). - Defend in EJS by checking
Array.isArray(agentData)before using.length. - Make sure my “Edit” links match the route paths (
/admin/edit/:idin both the EJS and router).
My Working Example
Here’s the fixed, minimal version with some extra features I added for practice.
app.js
const path = require('path');
const express = require('express');
const mysql = require('mysql2/promise');
const app = express();
// DB pool
const db = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'yourpass',
database: 'yourdb',
});
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.urlencoded({ extended: false }));
app.set('db', db);
const routes = require('./routes');
app.use('/', routes);
app.use((req, res) => res.status(404).send('Not Found'));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`http://localhost:${PORT}`));
routes.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => res.render('login'));
router.post('/login', (req, res) => {
const { username, password } = req.body;
if (username == 2 && password == 2) {
return res.redirect('/admin');
}
res.status(401).json({ message: 'Auth fail' });
});
router.get('/admin', async (req, res, next) => {
try {
const db = req.app.get('db');
const [rows] = await db.query('SELECT * FROM agentavia ORDER BY id DESC');
res.render('adminhome', { title: 'Agent List', agentData: rows });
} catch (err) {
next(err);
}
});
router.get('/admin/edit/:id', async (req, res, next) => {
try {
const db = req.app.get('db');
const [rows] = await db.query('SELECT * FROM agentavia WHERE id = ?', [req.params.id]);
if (!rows.length) return res.status(404).send('Agent not found');
res.render('modifyAgent', { title: 'Edit Agent', editData: rows[0] });
} catch (err) {
next(err);
}
});
router.post('/admin/edit/:id', async (req, res, next) => {
try {
const db = req.app.get('db');
await db.query('UPDATE agentavia SET ? WHERE id = ?', [req.body, req.params.id]);
res.redirect('/admin');
} catch (err) {
next(err);
}
});
// Bonus practice features
router.get('/admin/create', (req, res) => res.render('modifyAgent', { title: 'Create Agent', editData: undefined }));
router.post('/admin/create', async (req, res, next) => {
try {
const db = req.app.get('db');
await db.query('INSERT INTO agentavia SET ?', [req.body]);
res.redirect('/admin');
} catch (err) {
next(err);
}
});
router.get('/admin/delete/:id', async (req, res, next) => {
try {
const db = req.app.get('db');
await db.query('DELETE FROM agentavia WHERE id = ?', [req.params.id]);
res.redirect('/admin');
} catch (err) {
next(err);
}
});
router.get('/admin/search', async (req, res, next) => {
try {
const { q = '' } = req.query;
const db = req.app.get('db');
const like = `%${q}%`;
const [rows] = await db.query(
`SELECT * FROM agentavia
WHERE agentName LIKE ? OR agentID LIKE ? OR agentStatus LIKE ?`,
[like, like, like]
); res.render(‘adminhome’, { title: `Search: ${q}`, agentData: rows }); } catch (err) { next(err); } }); module.exports = router;
views/adminhome.ejs (simplified with safe check)
<% const list = Array.isArray(agentData) ? agentData : []; %>
<% if (list.length) { %>
<% list.forEach((row, idx) => { %>
<tr>
<td><%= idx + 1 %></td>
<td><%= row.agentName %></td>
<td><%= row.agentID %></td>
<td><%= row.agentStatus %></td>
<td>
<a href="/admin/edit/<%= row.id %>">Edit</a> |
<a href="/admin/delete/<%= row.id %>">Delete</a>
</td>
</tr>
<% }) %>
<% } else { %>
<tr><td colspan="5">No Data Found</td></tr>
<% } %>
Final Thought
In the end, fixing the agentData is not defined error came down to keeping my routes consistent, always passing the right data into my views, and writing defensive EJS checks. Once I streamlined my /admin route and cleaned up the render logic, everything worked smoothly. It was a good reminder that most “undefined” errors aren’t about HTML being wrong they’re about how data flows between the backend and the templates.