Juergen Kunz bbb57004d9
Some checks failed
Default (tags) / security (push) Failing after 24s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
BREAKING CHANGE(core): major architectural refactoring with fetch-like API
2025-07-27 21:23:20 +00:00
2023-11-09 21:22:57 +01:00
2022-02-15 18:57:42 +01:00
2020-08-24 12:01:38 +00:00
2024-04-14 03:54:39 +02:00
2023-11-09 21:22:57 +01:00

@push.rocks/smartrequest

A modern HTTP/HTTPS request library for Node.js with support for form data, file uploads, JSON, binary data, streams, and unix sockets. Features both a legacy API for backward compatibility and a modern fetch-like API for new projects.

Install

To install @push.rocks/smartrequest, use one of the following commands:

# Using npm
npm install @push.rocks/smartrequest --save

# Using pnpm
pnpm add @push.rocks/smartrequest

# Using yarn
yarn add @push.rocks/smartrequest

This will add @push.rocks/smartrequest to your project's dependencies.

Key Features

  • 🚀 Modern Fetch-like API - Familiar response methods (.json(), .text(), .arrayBuffer(), .stream())
  • 🔄 Two API Styles - Legacy function-based API and modern fluent chainable API
  • 🌐 Unix Socket Support - Connect to local services like Docker
  • 📦 Form Data & File Uploads - Built-in support for multipart/form-data
  • 🔁 Pagination Support - Multiple strategies (offset, cursor, Link headers)
  • Keep-Alive Connections - Efficient connection pooling
  • 🛡️ TypeScript First - Full type safety and IntelliSense support
  • 🎯 Zero Magic Defaults - Explicit configuration following fetch API principles
  • 🔌 Streaming Support - Handle large files and real-time data
  • 🔧 Highly Configurable - Timeouts, retries, headers, and more

Usage

@push.rocks/smartrequest is designed as a versatile, modern HTTP client library for making HTTP/HTTPS requests in Node.js environments. It provides a clean, type-safe API inspired by the native fetch API but with additional features needed for server-side applications.

The library provides two distinct APIs:

  1. Legacy API - Simple function-based API for quick requests and backward compatibility
  2. Modern Fluent API - A chainable, fetch-like API for more complex scenarios

Below we will cover key usage scenarios of @push.rocks/smartrequest, showcasing its capabilities and providing you with a solid starting point to integrate it into your projects.

Import Guide

// Modern API (recommended for new projects)
import { SmartRequestClient } from '@push.rocks/smartrequest';

// Legacy API (for backward compatibility)
import { getJson, postJson, request } from '@push.rocks/smartrequest/legacy';

Simple GET Request

For fetching data from a REST API or any web service that returns JSON:

import { getJson } from '@push.rocks/smartrequest/legacy';

async function fetchGitHubUserInfo(username: string) {
  const response = await getJson(`https://api.github.com/users/${username}`);
  console.log(response.body); // The body contains the JSON response
}

fetchGitHubUserInfo('octocat');

The getJson function simplifies the process of sending a GET request and parsing the JSON response.

POST Requests with JSON

When you need to send JSON data to a server, for example, creating a new resource:

import { postJson } from '@push.rocks/smartrequest/legacy';

async function createTodoItem(todoDetails: { title: string; completed: boolean }) {
  const response = await postJson('https://jsonplaceholder.typicode.com/todos', {
    requestBody: todoDetails
  });
  console.log(response.body); // Log the created todo item
}

createTodoItem({ title: 'Implement smartrequest', completed: false });

postJson handles setting the appropriate content-type header and stringifies the JSON body.

Handling Form Data and File Uploads

@push.rocks/smartrequest simplifies the process of uploading files and submitting form data to a server:

import { postFormData, IFormField } from '@push.rocks/smartrequest/legacy';

async function uploadProfilePicture(formDataFields: IFormField[]) {
  await postFormData('https://api.example.com/upload', {}, formDataFields);
}

uploadProfilePicture([
  { name: 'avatar', type: 'filePath', payload: './path/to/avatar.jpg', fileName: 'avatar.jpg', contentType: 'image/jpeg' },
  { name: 'user_id', type: 'string', payload: '12345' }
]);

Streaming Support

For cases when dealing with large datasets or streaming APIs, @push.rocks/smartrequest provides streaming capabilities:

import { getStream } from '@push.rocks/smartrequest/legacy';

async function streamLargeFile(url: string) {
  const stream = await getStream(url);

  stream.on('data', (chunk) => {
    console.log('Received chunk of data.');
  });

  stream.on('end', () => {
    console.log('Stream ended.');
  });
}

streamLargeFile('https://example.com/largefile');

getStream allows you to handle data as it's received, which can be beneficial for performance and scalability.

Advanced Options and Customization

@push.rocks/smartrequest is built to be flexible, allowing you to specify additional options to tailor requests to your needs:

import { request, ISmartRequestOptions } from '@push.rocks/smartrequest/legacy';

async function customRequestExample() {
  const options: ISmartRequestOptions = {
    method: 'GET',
    headers: {
      'Custom-Header': 'Value'
    },
    keepAlive: true // Enables connection keep-alive
  };

  const response = await request('https://example.com/data', options);
  console.log(response.body);
}

customRequestExample();

request is the underlying function that powers the simpler getJson, postJson, etc., and provides you with full control over the HTTP request.

Modern Fluent API

In addition to the legacy API shown above, @push.rocks/smartrequest provides a modern, fluent API with a fetch-like response interface that offers a more chainable and TypeScript-friendly approach to making HTTP requests.

Basic Usage with the Modern API

import { SmartRequestClient } from '@push.rocks/smartrequest';

// Simple GET request
async function fetchUserData(userId: number) {
  const response = await SmartRequestClient.create()
    .url(`https://jsonplaceholder.typicode.com/users/${userId}`)
    .get();

  // Use the fetch-like response API
  const userData = await response.json();
  console.log(userData); // The parsed JSON response
}

// POST request with JSON body
async function createPost(title: string, body: string, userId: number) {
  const response = await SmartRequestClient.create()
    .url('https://jsonplaceholder.typicode.com/posts')
    .json({ title, body, userId })
    .post();

  const createdPost = await response.json();
  console.log(createdPost); // The created post
}

Setting Headers and Query Parameters

import { SmartRequestClient } from '@push.rocks/smartrequest';

async function searchRepositories(query: string, perPage: number = 10) {
  const response = await SmartRequestClient.create()
    .url('https://api.github.com/search/repositories')
    .header('Accept', 'application/vnd.github.v3+json')
    .query({
      q: query,
      per_page: perPage.toString()
    })
    .get();

  const data = await response.json();
  return data.items;
}

Handling Timeouts and Retries

import { SmartRequestClient } from '@push.rocks/smartrequest';

async function fetchWithRetry(url: string) {
  const response = await SmartRequestClient.create()
    .url(url)
    .timeout(5000) // 5 seconds timeout
    .retry(3) // Retry up to 3 times on failure
    .get();

  return await response.json();
}

Working with Different Response Types

The modern API provides a fetch-like interface for handling different response types:

import { SmartRequestClient } from '@push.rocks/smartrequest';

// JSON response (default)
async function fetchJson(url: string) {
  const response = await SmartRequestClient.create()
    .url(url)
    .get();
  
  return await response.json(); // Parses JSON automatically
}

// Text response
async function fetchText(url: string) {
  const response = await SmartRequestClient.create()
    .url(url)
    .get();
  
  return await response.text(); // Returns response as string
}

// Binary data
async function downloadImage(url: string) {
  const response = await SmartRequestClient.create()
    .url(url)
    .accept('binary') // Optional: hints to server we want binary
    .get();

  const buffer = await response.arrayBuffer();
  return Buffer.from(buffer); // Convert ArrayBuffer to Buffer if needed
}

// Streaming response
async function streamLargeFile(url: string) {
  const response = await SmartRequestClient.create()
    .url(url)
    .get();

  // Get the underlying Node.js stream
  const stream = response.stream();
  
  stream.on('data', (chunk) => {
    console.log(`Received ${chunk.length} bytes of data`);
  });

  return new Promise((resolve, reject) => {
    stream.on('end', resolve);
    stream.on('error', reject);
  });
}

Pagination Support

The modern API includes built-in support for various pagination strategies:

import { SmartRequestClient, PaginationStrategy } from '@push.rocks/smartrequest';

// Offset-based pagination (page & limit)
async function fetchAllUsers() {
  const client = SmartRequestClient.create()
    .url('https://api.example.com/users')
    .withOffsetPagination({
      pageParam: 'page',
      limitParam: 'limit',
      startPage: 1,
      pageSize: 20,
      totalPath: 'meta.total'
    });

  // Get first page with pagination info
  const firstPage = await client.getPaginated();
  console.log(`Found ${firstPage.items.length} users on first page`);
  console.log(`Has more pages: ${firstPage.hasNextPage}`);

  if (firstPage.hasNextPage) {
    // Get next page
    const secondPage = await firstPage.getNextPage();
    console.log(`Found ${secondPage.items.length} more users`);
  }

  // Or get all pages at once (use with caution for large datasets)
  const allUsers = await client.getAllPages();
  console.log(`Retrieved ${allUsers.length} users in total`);
}

// Cursor-based pagination
async function fetchAllPosts() {
  const allPosts = await SmartRequestClient.create()
    .url('https://api.example.com/posts')
    .withCursorPagination({
      cursorParam: 'cursor',
      cursorPath: 'meta.nextCursor',
      hasMorePath: 'meta.hasMore'
    })
    .getAllPages();

  console.log(`Retrieved ${allPosts.length} posts in total`);
}

// Link header-based pagination (GitHub API style)
async function fetchAllIssues(repo: string) {
  const paginatedResponse = await SmartRequestClient.create()
    .url(`https://api.github.com/repos/${repo}/issues`)
    .header('Accept', 'application/vnd.github.v3+json')
    .withLinkPagination()
    .getPaginated();

  return paginatedResponse.getAllPages();
}

Advanced Features

Unix Socket Support

import { SmartRequestClient } from '@push.rocks/smartrequest';

// Connect to a service via Unix socket
async function queryViaUnixSocket() {
  const response = await SmartRequestClient.create()
    .url('http://unix:/var/run/docker.sock:/v1.24/containers/json')
    .get();
  
  return await response.json();
}

Form Data with File Uploads

import { SmartRequestClient } from '@push.rocks/smartrequest';

async function uploadMultipleFiles(files: Array<{name: string, path: string}>) {
  const formFields = files.map(file => ({
    name: 'files',
    value: fs.readFileSync(file.path),
    filename: file.name,
    contentType: 'application/octet-stream'
  }));

  const response = await SmartRequestClient.create()
    .url('https://api.example.com/upload')
    .formData(formFields)
    .post();
  
  return await response.json();
}

Keep-Alive Connections

import { SmartRequestClient } from '@push.rocks/smartrequest';

// Enable keep-alive for better performance with multiple requests
async function performMultipleRequests() {
  const client = SmartRequestClient.create()
    .header('Connection', 'keep-alive');

  // Requests will reuse the same connection
  const results = await Promise.all([
    client.url('https://api.example.com/endpoint1').get(),
    client.url('https://api.example.com/endpoint2').get(),
    client.url('https://api.example.com/endpoint3').get()
  ]);

  return Promise.all(results.map(r => r.json()));
}

Response Object Methods

The modern API returns a SmartResponse object with the following methods:

  • json<T>(): Promise<T> - Parse response as JSON
  • text(): Promise<string> - Get response as text
  • arrayBuffer(): Promise<ArrayBuffer> - Get response as ArrayBuffer
  • stream(): NodeJS.ReadableStream - Get the underlying Node.js stream
  • raw(): http.IncomingMessage - Get the raw http.IncomingMessage

Each body method can only be called once per response, similar to the fetch API.

Through its comprehensive set of features tailored for modern web development, @push.rocks/smartrequest aims to provide developers with a powerful tool for handling HTTP/HTTPS requests efficiently. Whether it's a simple API call, handling form data, processing streams, or working with paginated APIs, @push.rocks/smartrequest delivers a robust, type-safe solution to fit your project's requirements.

Migration Guide: Legacy API to Modern API

If you're currently using the legacy API and want to migrate to the modern fluent API, here's a quick reference guide:

Legacy API Modern API
getJson(url) SmartRequestClient.create().url(url).get()
postJson(url, { requestBody: data }) SmartRequestClient.create().url(url).json(data).post()
putJson(url, { requestBody: data }) SmartRequestClient.create().url(url).json(data).put()
delJson(url) SmartRequestClient.create().url(url).delete()
postFormData(url, {}, fields) SmartRequestClient.create().url(url).formData(fields).post()
getStream(url) SmartRequestClient.create().url(url).accept('stream').get()
request(url, options) SmartRequestClient.create().url(url).[...configure options...].get()

The modern API provides more flexibility and better TypeScript integration, making it the recommended approach for new projects.

Complete Examples

Building a REST API Client

Here's a complete example of building a typed API client using smartrequest:

import { SmartRequestClient, type SmartResponse } from '@push.rocks/smartrequest';

interface User {
  id: number;
  name: string;
  email: string;
}

interface Post {
  id: number;
  title: string;
  body: string;
  userId: number;
}

class BlogApiClient {
  private baseUrl = 'https://jsonplaceholder.typicode.com';
  
  private async request(path: string) {
    return SmartRequestClient.create()
      .url(`${this.baseUrl}${path}`)
      .header('Accept', 'application/json');
  }
  
  async getUser(id: number): Promise<User> {
    const response = await this.request(`/users/${id}`).get();
    return response.json<User>();
  }
  
  async createPost(post: Omit<Post, 'id'>): Promise<Post> {
    const response = await this.request('/posts')
      .json(post)
      .post();
    return response.json<Post>();
  }
  
  async deletePost(id: number): Promise<void> {
    const response = await this.request(`/posts/${id}`).delete();
    
    if (!response.ok) {
      throw new Error(`Failed to delete post: ${response.statusText}`);
    }
  }
  
  async getAllPosts(userId?: number): Promise<Post[]> {
    const client = this.request('/posts');
    
    if (userId) {
      client.query({ userId: userId.toString() });
    }
    
    const response = await client.get();
    return response.json<Post[]>();
  }
}

// Usage
const api = new BlogApiClient();
const user = await api.getUser(1);
const posts = await api.getAllPosts(user.id);

Error Handling

import { SmartRequestClient } from '@push.rocks/smartrequest';

async function fetchWithErrorHandling(url: string) {
  try {
    const response = await SmartRequestClient.create()
      .url(url)
      .timeout(5000)
      .retry(2)
      .get();
    
    // Check if request was successful
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    
    // Handle different content types
    const contentType = response.headers['content-type'];
    
    if (contentType?.includes('application/json')) {
      return await response.json();
    } else if (contentType?.includes('text/')) {
      return await response.text();
    } else {
      return await response.arrayBuffer();
    }
  } catch (error) {
    if (error.code === 'ECONNREFUSED') {
      console.error('Connection refused - is the server running?');
    } else if (error.code === 'ETIMEDOUT') {
      console.error('Request timed out');
    } else {
      console.error('Request failed:', error.message);
    }
    throw error;
  }
}

This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the license file within this repository.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.

Company Information

Task Venture Capital GmbH Registered at District court Bremen HRB 35230 HB, Germany

For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

Description
A module for modern HTTP/HTTPS requests with support for form data, file uploads, JSON, binary data, streams, and more.
Readme 1.4 MiB
Languages
TypeScript 100%