How to fix Error: write after end in Node.js

Node.jsINTERMEDIATEMEDIUMUpdated Dec 9, 2025· 174 views

This error occurs when attempting to write data to a writable stream after it has been closed with end() or destroy(). Once a stream is ended, it cannot accept any additional data.

Quick fix

Fixes ERR_STREAM_WRITE_AFTER_END

const stream = fs.createWriteStream('output.txt');

stream.on('finish', () => {
  console.log('Stream finished at:', new Date().toISOString());
});

stream.on('error', (err) => {
  console.error('Stream error:', err);
});

// Log before ending
console.log('About to end stream');
stream.end();
  1. Identify where the stream is being ended prematurely
  2. Check for async operations writing to streams
  3. Prevent multiple end() calls on HTTP responses
  4. Handle stream events properly
node.js — error
const stream = fs.createWriteStream('output.txt');
stream.on('finish', () => {
console.log('Stream finished at:', new Date().toISOString());
});
stream.on('error', (err) => {
console.error('Stream error:', err);
});
// Log before ending
console.log('About to end stream');
stream.end();
Error: write after end

What this error means

The "write after end" error indicates that your code is trying to write data to a Node.js writable stream that has already been closed. In Node.js streams, once you call the end() method on a writable stream, it signals that no more data will be written. Any subsequent attempts to write to that stream will trigger this error. This is a protection mechanism to prevent data corruption and ensure stream lifecycle is managed correctly. Writable streams maintain an internal state, and once they transition to the "ended" state, they cannot return to accepting writes. The error typically manifests as "Error [ERR_STREAM_WRITE_AFTER_END]: write after end" and will crash your Node.js process if not handled properly. It commonly occurs in HTTP responses, file writing operations, and custom stream implementations.

Typical symptoms

  • Application crashes with "Error: write after end" message
  • HTTP responses fail to send after end() was called
  • File write operations throw ERR_STREAM_WRITE_AFTER_END error
  • Stream piping fails intermittently in asynchronous code
  • Express/Fastify routes crash when sending response multiple times
  • Logs show attempts to write to finished/destroyed streams

Common causes

  1. Calling stream.write() after stream.end() has been invoked
  2. Asynchronous operations (setTimeout, promises) writing to already-closed streams
  3. Multiple response.end() calls in Express/HTTP handlers
  4. Event handlers firing after stream has been closed
  5. Race conditions between async operations and stream lifecycle
  6. Error handlers attempting to write after stream destruction
  7. Middleware or interceptors modifying response after it was sent
  8. Unhandled promise rejections that trigger late write attempts

How to fix "Error: write after end"

1Identify where the stream is being ended prematurely

Trace your code to find all locations where end() or destroy() is called on the stream. Add logging to track the stream lifecycle:

javascript
const stream = fs.createWriteStream('output.txt');

stream.on('finish', () => {
  console.log('Stream finished at:', new Date().toISOString());
});

stream.on('error', (err) => {
  console.error('Stream error:', err);
});

// Log before ending
console.log('About to end stream');
stream.end();

For HTTP responses in Express:

javascript
app.get('/api/data', (req, res) => {
  console.log('Handler started');

  // Track if response was already sent
  res.on('finish', () => {
    console.log('Response finished');
  });

  res.json({ data: 'value' }); // This calls end() internally

  // This would fail:
  // res.send('more data'); // Error: write after end
});
2Check for async operations writing to streams

Ensure all asynchronous operations complete before ending the stream. Use async/await to control execution order:

❌ Wrong - Race condition:

javascript
const stream = fs.createWriteStream('log.txt');

stream.write('Starting...\n');

setTimeout(() => {
  stream.write('Delayed write\n'); // May fail if stream already ended
}, 100);

stream.end(); // Ends immediately, doesn't wait for timeout

✅ Correct - Wait for async operations:

javascript
const stream = fs.createWriteStream('log.txt');

stream.write('Starting...\n');

await new Promise((resolve) => {
  setTimeout(() => {
    stream.write('Delayed write\n');
    resolve(undefined);
  }, 100);
});

stream.end(); // Now safe to end

✅ Better - Use callback or promise pattern:

javascript
function writeToStream(stream, data) {
  return new Promise((resolve, reject) => {
    if (!stream.write(data)) {
      stream.once('drain', resolve);
    } else {
      resolve(undefined);
    }
  });
}

const stream = fs.createWriteStream('log.txt');
await writeToStream(stream, 'Data 1\n');
await writeToStream(stream, 'Data 2\n');
stream.end();
3Prevent multiple end() calls on HTTP responses

In web frameworks, ensure you only send one response per request. Use guards to prevent duplicate sends:

✅ Using a flag:

javascript
app.get('/api/data', async (req, res) => {
  let responseSent = false;

  try {
    const data = await fetchData();

    if (!responseSent) {
      responseSent = true;
      res.json({ data });
    }
  } catch (error) {
    if (!responseSent) {
      responseSent = true;
      res.status(500).json({ error: error.message });
    }
  }
});

✅ Check res.headersSent:

javascript
app.use((err, req, res, next) => {
  if (res.headersSent) {
    console.error('Response already sent, cannot handle error');
    return next(err);
  }

  res.status(500).json({ error: err.message });
});

✅ Return early after sending:

javascript
app.post('/api/process', async (req, res) => {
  if (!req.body.data) {
    return res.status(400).json({ error: 'Missing data' });
    // Return prevents further execution
  }

  const result = await processData(req.body.data);
  return res.json({ result });
});
4Handle stream events properly

Listen to stream events and manage state to prevent writes after closure:

javascript
class SafeStreamWriter {
  constructor(stream) {
    this.stream = stream;
    this.ended = false;

    stream.on('finish', () => {
      this.ended = true;
    });

    stream.on('error', (err) => {
      this.ended = true;
      console.error('Stream error:', err);
    });
  }

  write(data) {
    if (this.ended) {
      console.warn('Attempted write to ended stream, ignoring');
      return false;
    }

    return this.stream.write(data);
  }

  end(data) {
    if (this.ended) {
      console.warn('Stream already ended');
      return;
    }

    this.ended = true;
    this.stream.end(data);
  }
}

// Usage
const fileStream = fs.createWriteStream('output.txt');
const writer = new SafeStreamWriter(fileStream);

writer.write('Line 1\n');
writer.write('Line 2\n');
writer.end();

// Safe - won't throw error
writer.write('This is ignored safely');
5Use pipeline for safer stream handling

The stream.pipeline() method automatically handles stream lifecycle and error cleanup:

❌ Manual piping (error-prone):

javascript
const readStream = fs.createReadStream('input.txt');
const transformStream = new Transform({ /* ... */ });
const writeStream = fs.createWriteStream('output.txt');

readStream.pipe(transformStream).pipe(writeStream);

// Errors may cause inconsistent state

✅ Using pipeline (safer):

javascript
const { pipeline } = require('stream/promises');

async function processFile() {
  try {
    await pipeline(
      fs.createReadStream('input.txt'),
      new Transform({
        transform(chunk, encoding, callback) {
          this.push(chunk.toString().toUpperCase());
          callback();
        }
      }),
      fs.createWriteStream('output.txt')
    );

    console.log('Pipeline succeeded');
  } catch (err) {
    console.error('Pipeline failed:', err);
    // All streams are properly cleaned up
  }
}

processFile();

For HTTP responses with streams:

javascript
const { pipeline } = require('stream');

app.get('/download', (req, res) => {
  const fileStream = fs.createReadStream('large-file.zip');

  pipeline(
    fileStream,
    res,
    (err) => {
      if (err) {
        console.error('Pipeline error:', err);
        // Stream already cleaned up
        if (!res.headersSent) {
          res.status(500).end();
        }
      }
    }
  );
});

Pipeline automatically handles backpressure, errors, and proper cleanup of all streams in the chain.

Advanced notes

Stream State Management:
Writable streams have internal states: writable, ended, finished, and destroyed. Understanding these states helps prevent write-after-end errors. The 'ended' state means end() was called but data might still be flushing. The 'finished' state means all data has been flushed. Check stream.writableEnded and stream.writableFinished properties to inspect state.

Backpressure Handling:
When write() returns false, the stream's internal buffer is full. Continuing to write without waiting for the 'drain' event can lead to memory issues and timing problems that may cause write-after-end errors when cleanup occurs unexpectedly.

HTTP Response Specifics:
In Express and similar frameworks, methods like res.json(), res.send(), res.redirect(), and res.end() all close the response stream. Calling any of these multiple times or in combination will trigger this error. Middleware execution continues after sending a response unless you explicitly return.

Testing Stream Code:
Use tools like Node.js's built-in test runner or Jest to test stream operations. Mock streams with PassThrough to simulate various scenarios. Always test error paths and async edge cases.

Debugging Techniques:
Enable Node.js async stack traces with --async-stack-traces flag to see the full call chain leading to the error. Use the 'async_hooks' module to track async operations that might outlive streams. Consider using stream.finished() utility to detect when a stream is no longer writable.

Frequently asked questions

What does "ERR_STREAM_WRITE_AFTER_END" mean?
js writable stream that has already been closed. js streams, once you call the end() method on a writable stream, it signals that no more data will be written. Any subsequent attempts to write to that stream will trigger this error.
How do I fix "ERR_STREAM_WRITE_AFTER_END"?
This error occurs when attempting to write data to a writable stream after it has been closed with end() or destroy(). Once a stream is ended, it cannot accept any additional data.
What causes "ERR_STREAM_WRITE_AFTER_END"?
Calling stream.write() after stream.end() has been invoked Asynchronous operations (setTimeout, promises) writing to already-closed streams Multiple response.end() calls in Express/HTTP handlers
Is the "ERR_STREAM_WRITE_AFTER_END" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading