Using the Fetch API with Undici in Node.js
Introduction
Undici is an HTTP client library that powers the fetch API in Node.js. It was written from scratch and does not rely on the built-in HTTP client in Node.js. It includes a number of features that make it a good choice for high-performance applications.
Basic GET Usage
async function main() {
// Like the browser fetch API, the default method is GET
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await response.json();
console.log(data);
// returns something like:
// {
// userId: 1,
// id: 1,
// title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
// body: 'quia et suscipit\n' +
// 'suscipit recusandae consequuntur expedita et cum\n' +
// 'reprehenderit molestiae ut ut quas totam\n' +
// 'nostrum rerum est autem sunt rem eveniet architecto'
// }
}
main().catch(console.error);
Basic POST Usage
// Data sent from the client to the server
const body = {
title: 'foo',
body: 'bar',
userId: 1,
};
async function main() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'User-Agent': 'undici-stream-example',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const data = await response.json();
console.log(data);
// returns something like:
// { title: 'foo', body: 'bar', userId: 1, id: 101 }
}
main().catch(console.error);
Customizing the Fetch API with Undici
Undici allows you to customize the Fetch API by providing options to the fetch
function. For example, you can set custom headers, set the request method, and set the request body. Here is an example of how you can customize the Fetch API with Undici:
The fetch function takes two arguments: the URL to fetch and an options object. The options object is the Request object that you can use to customize the request. The function returns a Promises that resolves to a Response object. One difference between the Fetch API in the browser and the Fetch API in Node.js is that the Node.js version does not support
In the following example, we are sending a POST request to the Ollama API with a JSON payload. Ollama is a cli tool that allows you to run LLM's (Large Language Models) on your local machine. You can download it here
ollama run mistral
This will download the mistral
model and run it on your local machine.
With a pool, you can reuse connections to the same server, which can improve performance. Here is an example of how you can use a pool with Undici:
import { Pool } from 'undici';
const ollamaPool = new Pool('http://localhost:11434', {
connections: 10,
});
/**
* Stream the completion of a prompt using the Ollama API.
* @param {string} prompt - The prompt to complete.
* @link https://github.com/ollama/ollama/blob/main/docs/api.md
**/
async function streamOllamaCompletion(prompt) {
const { statusCode, body } = await ollamaPool.request({
path: '/api/generate',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt, model: 'mistral' }),
});
// You can read about HTTP status codes here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
// 200 means the request was successful.
if (statusCode !== 200) {
throw new Error(`Ollama request failed with status ${statusCode}`);
}
let partial = '';
const decoder = new TextDecoder();
for await (const chunk of body) {
partial += decoder.decode(chunk, { stream: true });
console.log(partial);
}
console.log('Streaming complete.');
}
try {
await streamOllamaCompletion('What is recursion?');
} catch (error) {
console.error('Error calling Ollama:', error);
} finally {
console.log('Closing Ollama pool.');
ollamaPool.close();
}
Streaming Responses with Undici
Streams is a feature in Node.js that allows you to read and write chucks of data.
import { stream } from 'undici';
import { Writable } from 'stream';
async function fetchGitHubRepos() {
const url = 'https://api.github.com/users/nodejs/repos';
const { statusCode } = await stream(
url,
{
method: 'GET',
headers: {
'User-Agent': 'undici-stream-example',
Accept: 'application/json',
},
},
() => {
let buffer = '';
return new Writable({
write(chunk, encoding, callback) {
buffer += chunk.toString();
try {
const json = JSON.parse(buffer);
console.log(
'Repository Names:',
json.map(repo => repo.name)
);
buffer = '';
} catch (error) {
console.error('Error parsing JSON:', error);
}
callback();
},
final(callback) {
console.log('Stream processing completed.');
callback();
},
});
}
);
console.log(`Response status: ${statusCode}`);
}
fetchGitHubRepos().catch(console.error);