CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-got

Human-friendly and powerful HTTP request library for Node.js

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

hooks.mddocs/

Hooks System

Request lifecycle hooks for customizing behavior at different stages of the request process, enabling middleware-like functionality and advanced request/response processing.

Capabilities

Hooks Interface

The main hooks interface defining all available hook types and their execution points.

/**
 * Request lifecycle hooks configuration
 */
interface Hooks {
  /**
   * Called with plain request options before normalization
   * @default []
   */
  init?: InitHook[];
  
  /**
   * Called before the request is made
   * @default []
   */
  beforeRequest?: BeforeRequestHook[];
  
  /**
   * Called before following a redirect
   * @default []
   */
  beforeRedirect?: BeforeRedirectHook[];
  
  /**
   * Called before throwing an error
   * @default []
   */
  beforeError?: BeforeErrorHook[];
  
  /**
   * Called before retrying a request
   * @default []
   */
  beforeRetry?: BeforeRetryHook[];
  
  /**
   * Called after receiving a response
   * @default []
   */
  afterResponse?: AfterResponseHook[];
}

Init Hook

Called with plain request options before they are normalized and validated.

/**
 * Hook called during options initialization
 * @param init - Raw input options
 * @param self - Current Options instance
 */
type InitHook = (init: OptionsInit, self: Options) => void;

Usage Examples:

import got from "got";

const api = got.extend({
  hooks: {
    init: [
      (init, self) => {
        // Add default headers
        init.headers = {
          "User-Agent": "MyApp/1.0",
          ...init.headers
        };
        
        // Set default timeout if not specified
        if (!init.timeout) {
          init.timeout = { request: 10000 };
        }
        
        console.log("Initializing request to:", init.url);
      }
    ]
  }
});

// Init hook will be called for each request
await api.get("https://api.example.com/users");

Before Request Hook

Called before the HTTP request is made, allowing modification of final options.

/**
 * Hook called before making the HTTP request
 * @param options - Final normalized options
 * @returns Promise or void, or Response to short-circuit
 */
type BeforeRequestHook = (options: Options) => Promisable<void | Response | ResponseLike>;

type Promisable<T> = T | Promise<T>;

Usage Examples:

import got from "got";

const api = got.extend({
  hooks: {
    beforeRequest: [
      async (options) => {
        // Add authentication token
        const token = await getAuthToken();
        options.headers.Authorization = `Bearer ${token}`;
        
        // Log request details
        console.log(`→ ${options.method} ${options.url}`);
        console.log("Headers:", options.headers);
        
        // Validate required headers
        if (!options.headers.Authorization) {
          throw new Error("Authentication required");
        }
      },
      
      (options) => {
        // Add request timestamp
        options.headers["X-Request-Time"] = new Date().toISOString();
        
        // Modify URL based on environment
        if (process.env.NODE_ENV === "development") {
          options.url = options.url.replace("api.example.com", "dev-api.example.com");
        }
      }
    ]
  }
});

// Short-circuit response
const cachedApi = got.extend({
  hooks: {
    beforeRequest: [
      async (options) => {
        // Check cache before making request
        const cached = await getFromCache(options.url.toString());
        if (cached) {
          // Return cached response without making HTTP request
          return {
            statusCode: 200,
            body: cached.data,
            headers: cached.headers,
            requestUrl: options.url,
            ok: true
          } as Response;
        }
      }
    ]
  }
});

Before Redirect Hook

Called before following HTTP redirects, allowing modification of redirect options.

/**
 * Hook called before following a redirect
 * @param updatedOptions - Options for the redirect request  
 * @param plainResponse - Original response that triggered redirect
 * @returns Promise or void
 */
type BeforeRedirectHook = (updatedOptions: Options, plainResponse: PlainResponse) => Promisable<void>;

Usage Examples:

import got from "got";

const api = got.extend({
  hooks: {
    beforeRedirect: [
      (options, response) => {
        console.log(`Redirecting from ${response.requestUrl} to ${options.url}`);
        console.log(`Redirect status: ${response.statusCode}`);
        
        // Remove authorization header on external redirects
        const originalHost = response.requestUrl.hostname;
        const redirectHost = options.url.hostname;
        
        if (originalHost !== redirectHost) {
          console.log("External redirect detected, removing auth header");
          delete options.headers.Authorization;
        }
        
        // Limit redirect chains
        if (response.redirectUrls.length > 5) {
          throw new Error("Too many redirects");
        }
      }
    ]
  }
});

const response = await api.get("https://example.com/redirect-chain");

Before Error Hook

Called before throwing request errors, allowing error modification or recovery.

/**
 * Hook called before throwing an error
 * @param error - The error that would be thrown
 * @returns Promise resolving to modified error or new error
 */
type BeforeErrorHook = (error: RequestError) => Promisable<RequestError>;

Usage Examples:

import got, { HTTPError, RequestError } from "got";

const api = got.extend({
  hooks: {
    beforeError: [
      async (error) => {
        // Add context to errors
        error.message = `[${new Date().toISOString()}] ${error.message}`;
        
        // Log errors
        console.error("Request failed:", {
          url: error.options?.url,
          method: error.options?.method,
          code: error.code,
          message: error.message
        });
        
        // Transform specific errors
        if (error instanceof HTTPError && error.response.statusCode === 401) {
          // Try to refresh token on 401
          try {
            await refreshAuthToken();
            // Create new error with helpful message
            const newError = new Error("Authentication expired, please retry");
            newError.name = "AuthenticationExpiredError";
            return newError as RequestError;
          } catch {
            // If refresh fails, return original error
            return error;
          }
        }
        
        // Add retry suggestion for network errors
        if (["ENOTFOUND", "ECONNREFUSED", "ETIMEDOUT"].includes(error.code)) {
          error.message += " (Consider retrying the request)";
        }
        
        return error;
      }
    ]
  }
});

try {
  await api.get("https://api.example.com/protected");
} catch (error) {
  console.log("Enhanced error:", error.message);
}

Before Retry Hook

Called before retrying failed requests, allowing retry customization.

/**
 * Hook called before retrying a request
 * @param error - The error that triggered the retry
 * @param retryCount - Current retry attempt number
 * @returns Promise or void
 */
type BeforeRetryHook = (error: RequestError, retryCount: number) => Promisable<void>;

Usage Examples:

import got, { HTTPError } from "got";

const api = got.extend({
  retry: {
    limit: 3,
    methods: ["GET", "POST", "PUT", "DELETE"]
  },
  hooks: {
    beforeRetry: [
      async (error, retryCount) => {
        console.log(`Retry attempt ${retryCount} for ${error.options?.url}`);
        console.log(`Error: ${error.code} - ${error.message}`);
        
        // Exponential backoff with jitter
        const delay = Math.min(1000 * Math.pow(2, retryCount), 10000);
        const jitter = Math.random() * 1000;
        
        console.log(`Waiting ${delay + jitter}ms before retry...`);
        await new Promise(resolve => setTimeout(resolve, delay + jitter));
        
        // Refresh auth token before retry on 401 errors
        if (error instanceof HTTPError && error.response.statusCode === 401) {
          console.log("Refreshing authentication token...");
          await refreshAuthToken();
          
          // Update authorization header in options
          const newToken = await getAuthToken();
          error.options.headers.Authorization = `Bearer ${newToken}`;
        }
        
        // Clear cache on server errors
        if (error instanceof HTTPError && error.response.statusCode >= 500) {
          console.log("Server error detected, clearing cache");
          await clearRequestCache();
        }
      }
    ]
  }
});

const response = await api.get("https://unreliable-api.example.com");

After Response Hook

Called after receiving responses, allowing response modification or additional processing.

/**
 * Hook called after receiving a response
 * @param response - The HTTP response
 * @param retryWithMergedOptions - Function to retry with new options
 * @returns Promise resolving to response or new response
 */
type AfterResponseHook<ResponseType = unknown> = (
  response: Response<ResponseType>,
  retryWithMergedOptions: (options: OptionsInit) => never
) => Promisable<Response | CancelableRequest<Response>>;

Usage Examples:

import got from "got";

const api = got.extend({
  hooks: {
    afterResponse: [
      async (response, retryWithMergedOptions) => {
        console.log(`← ${response.statusCode} ${response.requestUrl}`);
        console.log(`Response time: ${response.timings.phases.total}ms`);
        
        // Cache successful responses
        if (response.ok && response.request.options.method === "GET") {
          await cacheResponse(response.requestUrl.toString(), {
            data: response.body,
            headers: response.headers,
            timestamp: Date.now()
          });
        }
        
        // Handle rate limiting with retry
        if (response.statusCode === 429) {
          const retryAfter = response.headers["retry-after"];
          if (retryAfter) {
            const delay = parseInt(retryAfter as string) * 1000;
            console.log(`Rate limited, retrying after ${delay}ms`);
            
            await new Promise(resolve => setTimeout(resolve, delay));
            
            // Retry the request (this will throw and restart the request)
            retryWithMergedOptions({});
          }
        }
        
        // Transform response data
        if (response.headers["content-type"]?.includes("application/json")) {
          try {
            const data = typeof response.body === "string" 
              ? JSON.parse(response.body) 
              : response.body;
            
            // Add metadata to response
            const enhanced = {
              ...data,
              _metadata: {
                requestTime: response.timings.phases.total,
                fromCache: response.isFromCache,
                retryCount: response.retryCount
              }
            };
            
            // Return modified response
            return {
              ...response,
              body: enhanced
            };
          } catch {
            // Return original response if JSON parsing fails
            return response;
          }
        }
        
        return response;
      }
    ]
  }
});

const response = await api.get("https://api.example.com/data");
console.log(response.body._metadata); // Added by hook

Hook Composition and Ordering

Hooks execute in the order they are defined and can be composed across instance extensions.

import got from "got";

// Base hooks
const baseApi = got.extend({
  hooks: {
    beforeRequest: [
      (options) => {
        console.log("Base: Adding User-Agent");
        options.headers["User-Agent"] = "MyApp/1.0";
      }
    ],
    afterResponse: [
      (response) => {
        console.log("Base: Logging response");
        console.log(`Response: ${response.statusCode}`);
        return response;
      }
    ]
  }
});

// Extended hooks
const enhancedApi = baseApi.extend({
  hooks: {
    beforeRequest: [
      async (options) => {
        console.log("Enhanced: Adding auth token");
        const token = await getAuthToken();
        options.headers.Authorization = `Bearer ${token}`;
      }
    ],
    afterResponse: [
      async (response) => {
        console.log("Enhanced: Caching response");
        await cacheResponse(response.requestUrl.toString(), response.body);
        return response;
      }
    ]
  }
});

// Execution order:
// 1. Base beforeRequest hook (User-Agent)
// 2. Enhanced beforeRequest hook (Authorization)  
// 3. HTTP request is made
// 4. Base afterResponse hook (logging)
// 5. Enhanced afterResponse hook (caching)

await enhancedApi.get("https://api.example.com/data");

Advanced Hook Patterns

Middleware-style hook composition:

import got from "got";

// Create middleware functions
const addAuth = (token: string) => ({
  beforeRequest: [(options: Options) => {
    options.headers.Authorization = `Bearer ${token}`;
  }]
});

const addLogging = () => ({
  beforeRequest: [(options: Options) => {
    console.log(`→ ${options.method} ${options.url}`);
  }],
  afterResponse: [(response: Response) => {
    console.log(`← ${response.statusCode}`);
    return response;
  }]
});

const addRetryDelay = (baseDelay: number = 1000) => ({
  beforeRetry: [async (error: RequestError, retryCount: number) => {
    const delay = baseDelay * Math.pow(2, retryCount - 1);
    await new Promise(resolve => setTimeout(resolve, delay));
  }]
});

// Compose middleware
const api = got.extend({
  hooks: {
    ...addAuth("your-token"),
    ...addLogging(),
    ...addRetryDelay(500)
  }
});

Request/Response transformation pipeline:

import got from "got";

const api = got.extend({
  hooks: {
    beforeRequest: [
      // Transform request data
      (options) => {
        if (options.json && typeof options.json === "object") {
          // Convert camelCase to snake_case for API
          options.json = transformKeys(options.json, camelToSnake);
        }
      }
    ],
    afterResponse: [
      // Transform response data
      (response) => {
        if (response.body && typeof response.body === "object") {
          // Convert snake_case to camelCase for client
          const transformed = transformKeys(response.body, snakeToCamel);
          return {
            ...response,
            body: transformed
          };
        }
        return response;
      }
    ]
  }
});

// Helper functions
function transformKeys(obj: any, transformer: (key: string) => string): any {
  if (Array.isArray(obj)) {
    return obj.map(item => transformKeys(item, transformer));
  }
  if (obj && typeof obj === "object") {
    return Object.keys(obj).reduce((result, key) => {
      const newKey = transformer(key);
      result[newKey] = transformKeys(obj[key], transformer);
      return result;
    }, {} as any);
  }
  return obj;
}

const camelToSnake = (str: string) => str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
const snakeToCamel = (str: string) => str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());

Error Handling in Hooks

Proper error handling within hooks to avoid breaking request flow.

import got from "got";

const api = got.extend({
  hooks: {
    beforeRequest: [
      async (options) => {
        try {
          // Potentially failing operation
          const token = await getAuthToken();
          options.headers.Authorization = `Bearer ${token}`;
        } catch (error) {
          console.warn("Failed to get auth token:", error.message);
          // Continue without auth rather than failing
        }
      }
    ],
    afterResponse: [
      async (response) => {
        try {
          // Potentially failing cache operation
          await cacheResponse(response.requestUrl.toString(), response.body);
        } catch (error) {
          console.warn("Failed to cache response:", error.message);
          // Continue with response even if caching fails
        }
        return response;
      }
    ],
    beforeError: [
      (error) => {
        try {
          // Enhance error with additional context
          const enhancedError = new Error(`Enhanced: ${error.message}`);
          enhancedError.name = error.name;
          enhancedError.stack = error.stack;
          return enhancedError as RequestError;
        } catch {
          // Return original error if enhancement fails
          return error;
        }
      }
    ]
  }
});

docs

errors.md

hooks.md

http-methods.md

index.md

instances.md

options.md

pagination.md

responses.md

streams.md

utilities.md

tile.json