Tag Archives: web-development

How to make an API call that streams data

Using fetch

const response = await fetch("/endpoint", {
  signal: AbortSignal.timeout(10000),
});

const reader = response.body.getReader();

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

  if (done) {
    console.log("Stream complete");
    break;
  }

  console.log(new TextDecoder().decode(value));
}

XHR

var xhr = new XMLHttpRequest();
xhr.open('GET', '/endpoint', true);
xhr.timeout = 10000;
xhr.ontimeout = function() {
  console.error('Request timed out!');
};
xhr.onprogress = function() {
  var responseText = xhr.responseText;
  var chunk = responseText.slice(xhr.prevLen);
  xhr.prevLen = responseText.length;
  console.log(chunk);
};
xhr.onload = function() {
  console.log('Done!');
};
xhr.send();

Axios:

Not supported. Said to be impossible in 2009, closed in 2016, and there's still no support for it today.

General API:

async function GET(req, res) {
  res.write('hello')
  await new Promise((resolve) => setTimeout(resolve, 1000))
  res.write('world')
  res.end()
}

Optional: Next.js 14 endpoint:

import { NextApiResponse } from 'next'

// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
function iteratorToStream(iterator: any) {
  return new ReadableStream({
    async pull(controller) {
      const { value, done } = await iterator.next()

      if (done) {
        controller.close()
      } else {
        controller.enqueue(value)
      }
    },
  })
}

function sleep(time: number) {
  return new Promise((resolve) => {
    setTimeout(resolve, time)
  })
}

const encoder = new TextEncoder()

async function* makeIterator() {
  yield encoder.encode('<p>One</p>')
  await sleep(700)
  yield encoder.encode('<p>Two</p>')
  await sleep(700)
  yield encoder.encode('<p>Three</p>')
}

export default async function handler(req, res: NextApiResponse<any>) {
  const iterator = makeIterator()
  const stream = iteratorToStream(iterator)

  return new Response(stream)
}

export const runtime = 'edge'

How to make a Nextron App with API routes even in production/bundled mode

import dotenv from 'dotenv'
import { BrowserWindow, app, ipcMain } from 'electron'
import log from 'electron-log/main'
import * as http from 'http'
import * as net from 'net'
import next from 'next'
import path from 'path'
import { parse } from 'url'

const logger = log.scope('main')

const load = async () => {
  await app.whenReady()

  logger.info('App ready')

  const rendererPath = path.join(app.getAppPath(), 'renderer')
  const nextApp = next({ dev: !isProd, dir: rendererPath })
  const handle = nextApp.getRequestHandler()

  await nextApp.prepare()

  server = http.createServer((req: any, res: any) => {
    logger.info(`HTTP Request [${req.method}] ${req.url}`)
    const parsedUrl = parse(req.url, true)

    handle(req, res, parsedUrl)
  })

  const serverPort = await new Promise((resolve) => {
    server.listen(0, () => {
      const address = server.address() as net.AddressInfo
      logger.info(`  >> Ready on http://localhost:${address.port}`)
      logger.info(`Renderer path: ${rendererPath}`)

      resolve((server.address() as net.AddressInfo).port)
    })
  })

  global.baseUrl = `http://localhost:${serverPort}`
  global.port = serverPort
  process.env.NEXTAUTH_URL = global.baseUrl

  const mainWindow = await createMainWindow({
    url: global.baseUrl,
    isProd,
    allAppWindows,
    appInfo: {
      baseUrl: global.baseUrl,
      port: global.port,
    },
  })

  // do not use dev port or app://
  // use your server instead
  await mainWindowawait mainWindow.loadURL(`${global.baseUrl}/home`)
}

logger.info('Loading app...')
load()