HTTP Fundamentals: Routing fetch() to an in-memory server instance

Matteo CollinaMatteo Collina
3 min read

In web development, user interactions trigger backend actions, which rely heavily on efficient HTTP calls. Over time, HTTP has become the most popular interface largely due to its robust security measures and ease of debuggability.

In this blog, we’ll provide you with a quick guide to understanding the concept of HTTP and how to turbocharge it with Undici and Fastify.

HTTP: The Optimal Standard Interface

With the advent of RFC 9110, the semantics of the HTTP/1.1 and HTTP/2 protocols were distinctly delineated, accentuating the significance of the wiring format as a crucial implementation detail.

Despite the emergence of three major versions—HTTP/1.0, HTTP/1.1, and HTTP/2.0—none has rendered the others obsolete. Instead, they coexist, each offering specific benefits and limitations depending on the usage context. RFC 9110 coalesced all of them and provided shared semantics beyond individual protocols, extending HTTP's versatility beyond network boundaries and facilitating interactions through familiar libraries and APIs.

In this article, we are going to explain how we can route HTTP requests in memory, without the need to perform any I/O. To do so, we are going to use Undici and Fastify.

Undici

Undici is a modern HTTP/1.1 client library for Node.js, known for its efficiency and speed. Undici was designed to handle a large number of concurrent requests with low overhead, and is built using Node.js's asynchronous I/O capabilities, making it suitable for applications requiring high throughput and low latency. Undici boasts over 30 million monthly downloads.

Fastify

Fastify is one of the speediest web frameworks for Node.js, boasting an impressive average of 6 million monthly downloads. With 17 collaborators and currently in its fourth iteration, Fastify offers a robust platform for web development.

Fastify-undici-dispatcher

As we have seen in https://blog.platformatic.dev/http-fundamentals-understanding-undici-and-its-working-mechanism, the underpinning of Undici are the dispatchers. fastify-undici-dispatcher implements the Dispatcher interface to route HTTP requests targeting a local domain to a Fastify application. Below is a demo of how this works:

const { request, Agent, setGlobalDispatcher } = require('undici')
const server = Fastify()

server.get('/', async (req, reply) => {
  return 'hello world'
})

const dispatcher = new FastifyUndiciDispatcher({
  dispatcher: new Agent(), // optional
  domain: '.local' // optional
})
dispatcher.route('myserver', server)

setGlobalDispatcher(dispatcher)

const res = await fetch('http://myserver.local')
console.log(await res.text())

Can Undici dispatcher facilitate network HTTP implementation?

Notably, Fastify comes equipped with embedded support for request injection, facilitating seamless injection of HTTP requests. Below is a demonstration of this capability:

import fastify from "fastify"

const app = fastify();

app.get('/', async()=> 'hello world');

const res = await app.inject('/');
console.log(res.body.toString())

The FastifyUndiciDispatcher then internally calls the .inject() function to deliver the incoming HTTP request.

Fastify implements this capability with the light-my-request library which injects HTTP requests into a Node server. This library enables developers to mimic the functionality of http.createServer by passing in a function.

Below is a demonstration of integrating the library within a Node server.

const http = require('node:http')
const inject = require('light-my-request')

const dispatch = function (req, res) {
  const reply = 'Hello World'
  res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': reply.length })
  res.end(reply)
}

const server = http.createServer(dispatch)

inject(dispatch, { method: 'get', url: '/' }, (err, res) => {
  console.log(res.payload)
})

Wrapping up

HTTP calls form the backbone of production-level Node.js applications. Undici offers a standout Node.js HTTP library, as well as the underpinning of Node.js fetch() implementation.

Combining Fastify with Undici within the fastify-undici-dispatcher library allows to implement in-memory HTTP calls from fetch(), enabling new use cases as patterns.

Thanks to this library, Platformatic can implement its runtime and control functionality.

Want to learn more about HTTP Clients?

0
Subscribe to my newsletter

Read articles from Matteo Collina directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Matteo Collina
Matteo Collina