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

  1. Point sendFile at an actual file — typically append the filename: `res.sendFile(path.join(buildDir, 'index.html'))`.
  2. If serving user-derived paths, validate with `fs.stat` (or catch `err.code === 'EISDIR'` in the sendFile callback) and respond 404 for directories.
  3. If you want directory-index behavior (serve index.html for a folder), use `express.static(dir)` instead of hand-rolled sendFile routes.
  4. 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

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


AI-assisted analysis of expressjs/express@a3714473fe (2026-07-31). Data as JSON: /data/errors/68ac2f914a80b151.json. Report an issue: GitHub ↗.