How to Parse NDJSON Streams Using Axios

Newline Delimited JSON (NDJSON) delivers individual JSON objects separated by newline characters (\n), making it ideal for streaming large or real-time datasets. Because Axios attempts to parse entire HTTP responses as a single JSON object by default, standard configurations will throw a syntax error when encountering NDJSON. The recommended approach to parse NDJSON with Axios is to set the responseType to 'stream' in Node.js (or read the response body as a stream) and process the incoming chunks line-by-line using a stream splitter or the native readline module.

Why Standard Axios Fails with NDJSON

Axios automatically invokes JSON.parse() on response data when the server returns a JSON content type. Because NDJSON contains multiple adjacent JSON strings that are not wrapped in an array, standard parsing fails immediately. To process NDJSON successfully, you must:

  1. Disable automatic JSON transformation.
  2. Consume the HTTP response body as a stream.
  3. Buffer and split chunks on newline characters (\n).
  4. Parse each line independently with JSON.parse().

The cleanest native approach in Node.js uses the built-in readline module. It automatically buffers incoming chunks, identifies line breaks, and emits individual lines for parsing.

const axios = require('axios');
const readline = require('readline');

async function streamNDJSON(url) {
  try {
    const response = await axios({
      method: 'get',
      url: url,
      responseType: 'stream' // Tells Axios to return a Node.js Readable Stream
    });

    const rl = readline.createInterface({
      input: response.data,
      crlfDelay: Infinity // Recognizes all instances of CR LF (\r\n) as a single line break
    });

    rl.on('line', (line) => {
      const trimmed = line.trim();
      if (!trimmed) return; // Skip empty lines

      try {
        const jsonRecord = JSON.parse(trimmed);
        console.log('Parsed record:', jsonRecord);
      } catch (err) {
        console.error('Failed to parse line:', line, err.message);
      }
    });

    rl.on('close', () => {
      console.log('NDJSON stream finished.');
    });

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

  } catch (error) {
    console.error('Axios request failed:', error.message);
  }
}

streamNDJSON('https://example.com/data.ndjson');

Alternative Node.js Implementation: Using split2

For pipeline-based stream processing, the split2 library transforms raw byte streams into streams of individual lines that can be piped into a transform or writable stream.

npm install split2 through2
const axios = require('axios');
const split2 = require('split2');
const through2 = require('through2');

async function fetchWithPipeline(url) {
  const response = await axios({
    method: 'get',
    url: url,
    responseType: 'stream'
  });

  response.data
    .pipe(split2()) // Splits stream on \n
    .pipe(through2.obj((line, enc, cb) => {
      if (line.trim()) {
        try {
          const parsed = JSON.parse(line);
          // Process record
          console.log(parsed);
        } catch (e) {
          return cb(e);
        }
      }
      cb();
    }))
    .on('error', (err) => console.error('Pipeline error:', err))
    .on('finish', () => console.log('Processing complete.'));
}

Handling NDJSON in the Browser

In browser environments, Axios uses XMLHttpRequest by default, which does not support Node.js streams. To stream NDJSON in the browser using Axios, enable the fetch adapter (available in Axios v1.x+) and read the ReadableStreamDefaultReader:

import axios from 'axios';

async function streamBrowserNDJSON(url) {
  const response = await axios.get(url, {
    responseType: 'stream',
    adapter: 'fetch' // Requires Axios v1.7+ for native Fetch adapter
  });

  const reader = response.data.getReader();
  const decoder = new TextDecoder('utf-8');
  let buffer = '';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    
    // Retain incomplete last line in buffer
    buffer = lines.pop(); 

    for (const line of lines) {
      if (line.trim()) {
        const record = JSON.parse(line);
        console.log('Record:', record);
      }
    }
  }

  // Parse any remaining content in the buffer
  if (buffer.trim()) {
    console.log('Final Record:', JSON.parse(buffer));
  }
}

Best Practices