expressjs/express · error · Error
EISDIR
EISDIR
Error message
EISDIR, read
What it means
In the same `sendfile` helper, the `send` stream emits a `directory` event when the resolved path is a directory rather than a regular file. Express's `ondirectory` handler (lib/response.js:939-946) converts that into `new Error('EISDIR, read')` with `code: 'EISDIR'`, mimicking the Node.js fs error you would get from reading a directory. It means `res.sendFile()`/`res.download()` was pointed at a directory, which cannot be streamed as a file.
Source
Thrown at lib/response.js:943
var done = false;
var streaming;
// request aborted
function onaborted() {
if (done) return;
done = true;
var err = new Error('Request aborted');
err.code = 'ECONNABORTED';
callback(err);
}
// directory
function ondirectory() {
if (done) return;
done = true;
var err = new Error('EISDIR, read');
err.code = 'EISDIR';
callback(err);
}
// errors
function onerror(err) {
if (done) return;
done = true;
callback(err);
}
// ended
function onend() {
if (done) return;
done = true;
callback();
}
View on GitHub ↗ (pinned to a3714473fe)
Solutions
- Point sendFile at an actual file — typically append the filename: `res.sendFile(path.join(buildDir, 'index.html'))`.
- If serving user-derived paths, validate with `fs.stat` (or catch `err.code === 'EISDIR'` in the sendFile callback) and respond 404 for directories.
- If you want directory-index behavior (serve index.html for a folder), use `express.static(dir)` instead of hand-rolled sendFile routes.
- Check the deployed filesystem layout — confirm the target is a file, not a folder created by the build or extraction step.
Example fix
// before
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, 'build')); // 'build' is a directory -> EISDIR
});
// after
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
}); Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs');
const path = require('path');
const resolved = path.resolve(root, requestedName);
const stats = fs.statSync(resolved, { throwIfNoEntry: false });
if (!stats || !stats.isFile()) {
return res.status(404).end(); // directory or missing — don't call sendFile
}
res.sendFile(resolved); Type guard
function isEisdir(err) {
return err != null && typeof err === 'object' && err.code === 'EISDIR';
} Try / catch
res.sendFile(filePath, function (err) {
if (err) {
if (err.code === 'EISDIR' || err.code === 'ENOENT') {
return res.status(404).end(); // path is a directory or missing
}
next(err);
}
}); Prevention
- stat the resolved path and require stats.isFile() before calling res.sendFile — directories should map to a 404 (or an explicit index file), never reach sendFile
- Never build sendFile paths directly from user input; resolve against a fixed root and reject paths that escape it or end with a separator
- Map EISDIR to a 404 in the sendFile callback rather than leaking a 500 with filesystem details to the client
- If directory URLs should serve an index page, append index.html explicitly (or use express.static with its index option) instead of passing the directory to sendFile
- Add tests that request a known directory path (e.g. '/uploads/') to lock in the 404 behavior
When it happens
Trigger: `res.sendFile(path)` or `res.download(path)` where `path` resolves to a directory: passing the folder itself instead of a file inside it, building the path from user input or a route param that is empty/`.`/ends in a slash, or joining `root` with a relative segment that lands on a directory. Note Express does not follow directory-index behavior here — unlike `express.static`, sendFile never serves `index.html` for a directory.
Common situations: SPA fallbacks written as `res.sendFile(path.join(__dirname, 'build'))` (missing `'index.html'`); file-serving routes like `/files/:name` hit with an empty or dot name; deployment layouts where the expected file was replaced by a directory of the same name; refactors that changed a build output from a single bundle file to a folder.
Related errors
- Failed to lookup view "' + name + '" in views ' + dirs
- path argument is required to res.sendFile
- path must be a string to res.sendFile
- path must be absolute or specify root to res.sendFile
- ECONNABORTED
AI-assisted analysis of expressjs/express@a3714473fe (2026-07-31).
Data as JSON: /data/errors/68ac2f914a80b151.json.
Report an issue: GitHub ↗.