npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

restfit

v1.0.7

Published

A TypeScript decorator-based REST API client library

Readme

RestFit

A TypeScript decorator-based REST API client library that provides a clean and type-safe way to define API services.

Inspired by: Retrofit (Java/Android) and Refit (.NET)

Features

  • 🎯 Type-safe: Full TypeScript support with type inference
  • 🎨 Decorator-based: Clean, declarative API using decorators
  • 🔧 Flexible: Support for path parameters, query strings, headers, and request bodies
  • Error Handling: Custom error and success handlers for different HTTP status codes
  • 🛡️ Resilient: Built-in retry policies and circuit breaker patterns (similar to Refit)
  • 📦 Lightweight: Built on top of axios with minimal overhead

Installation

npm install restfit axios reflect-metadata axios-retry axios-circuit-breaker

Quick Start

import 'reflect-metadata';
import { Get, Post, Path, Query, Body, Header, createApiService } from 'restfit';

class UserService {
  @Get('/users/{userId}')
  async getUser(@Path('userId') userId: string): Promise<User> {
    // Implementation is handled by the decorator
  }

  @Post('/users')
  async createUser(@Body() user: User): Promise<User> {
    // Implementation is handled by the decorator
  }

  @Get('/users')
  async listUsers(
    @Query('page') page: number,
    @Query('limit') limit: number,
    @Header('Authorization') auth: string
  ): Promise<User[]> {
    // Implementation is handled by the decorator
  }
}

// Create an instance
const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json'
  },
  // Optional: Authorization
  authorization: 'your-token-here', // Static token
  // Or use a function for dynamic tokens:
  // authorization: async () => await getTokenFromStorage(),
  authorizationType: 'Bearer' // 'Bearer' | 'Basic' | 'Custom'
});

// Use it
const user = await userService.getUser('123');
const newUser = await userService.createUser({ name: 'John' });

// Or create multiple services at once
const api = createApiService(
  {
    baseUrl: 'https://api.example.com',
    headers: { 'Content-Type': 'application/json' }
  },
  {
    users: UserService,
    posts: PostService,
    comments: CommentService
  }
);

// Use them
const user = await api.users.getUser('123');
const posts = await api.posts.listPosts();

HTTP Method Decorators

  • @Get(path) - GET request
  • @Post(path) - POST request
  • @Put(path) - PUT request
  • @Patch(path) - PATCH request
  • @Delete(path) - DELETE request

Parameter Decorators

  • @Path(name) - Path parameter (e.g., /users/{userId})
  • @Query(name) - Query parameter (e.g., ?page=1)
  • @Body() - Request body
  • @Header(name) - Request header
  • @SerializedName(name) or @AliasAs(name) - Map property name to different JSON key (for request/response serialization)

Serialization Decorators

Use @SerializedName or @AliasAs to map TypeScript property names to different JSON keys:

class User {
  @SerializedName('first_name')
  firstName: string;

  @SerializedName('last_name')
  lastName: string;

  @AliasAs('email_address') // Alternative name
  email: string;
}

Handler Decorators

  • @OnError(handler) - Catch-all error handler (catches all errors regardless of status)
    • Example: @OnError((error) => { /* handle all errors */ })
  • @OnError(status, handler) - Custom error handler for specific status codes
    • status can be a single number, array of numbers, or null for catch-all
    • Example: @OnError([404, 500], handler) or @OnError(null, handler)
  • @OnSuccess(status, handler) - Custom success handler for specific status codes
    • status can be a single number or array of numbers
    • Example: @OnSuccess([200, 201], handler)

Response Interceptors

RestFit supports response interceptors that allow you to check responses, trigger actions, and optionally modify responses.

There are two ways to use interceptors:

  1. Global interceptors - Applied to all requests via ApiServiceConfig
  2. Method-specific interceptors - Applied via @ResponseInterceptor decorator

Both can be used together, with global interceptors running first.

Interceptors can:

  • Return void for side effects (logging, triggering actions, etc.)
  • Return a modified AxiosResponse to transform the response data

Global Response Interceptors

Configure interceptors at the service level to apply to all requests:

import { createApiService, ResponseInterceptorConfig } from 'restfit';
import { AxiosResponse } from 'axios';

const globalInterceptors: ResponseInterceptorConfig[] = [
  {
    // Handler: Check condition and execute action in one function
    handler: (response) => {
      if (response.headers['x-rate-limit-remaining']) {
        const remaining = response.headers['x-rate-limit-remaining'];
        const limit = response.headers['x-rate-limit-limit'];
        console.log(`Rate limit: ${remaining}/${limit}`);
        
        if (Number(remaining) < 10) {
          notificationService.warn('Rate limit is getting low!');
        }
      }
    }
  },
  {
    // Handler: Check for custom header and update app state
    handler: async (response) => {
      if (response.headers['x-something']) {
        const customValue = response.headers['x-something'];
        await appState.update({ customFlag: customValue });
      }
    }
  }
];

const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com',
  responseInterceptors: globalInterceptors
});

Method-Specific Interceptors (Decorator)

Use @ResponseInterceptor decorator for method-specific interceptors:

import { Get, ResponseInterceptor } from 'restfit';
import { AxiosResponse } from 'axios';

class UserService {
  @Get('/users')
  @ResponseInterceptor((response) => {
    // Side effect: Check condition and execute action (void return)
    if (response.data.length > 0) {
      console.log(`Retrieved ${response.data.length} users`);
    }
  })
  @ResponseInterceptor(async (response) => {
    // Side effect: Multiple interceptors can be chained
    // Check for specific header
    if (response.headers['x-custom']) {
      const value = response.headers['x-custom'];
      eventBus.emit('custom-event', { value });
    }
  })
  async getUsers(): Promise<User[]> {
    return [];
  }

  @Get('/users/{id}')
  @ResponseInterceptor((response) => {
    // Modify response: Transform the response data
    if (response.data) {
      response.data = {
        ...response.data,
        metadata: {
          fetchedAt: new Date().toISOString(),
          processed: true
        }
      };
      return response; // Return modified response
    }
  })
  async getUser(@Path('id') id: number): Promise<User & { metadata?: any }> {
    return {} as User & { metadata?: any };
  }
}

Use Cases:

  • Monitor rate limit headers and trigger warnings
  • Check for custom headers and update app state
  • Analyze response data and trigger notifications
  • Track analytics based on response characteristics
  • Update cache strategies based on cache-control headers
  • Global logging or monitoring across all API calls
  • Transform response data before it reaches your code
  • Add computed properties or metadata to responses
  • Normalize API responses to match your data models

Authorization Configuration

RestFit supports flexible authorization configuration:

// Static Bearer token
const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com',
  authorization: 'your-token-here',
  authorizationType: 'Bearer'
});

// Dynamic token (function)
const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com',
  authorization: async () => {
    // Get token from storage, refresh if needed, etc.
    return await getTokenFromStorage();
  },
  authorizationType: 'Bearer'
});

// Basic authentication
const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com',
  authorization: Buffer.from('username:password').toString('base64'),
  authorizationType: 'Basic'
});

// Custom authorization header
const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com',
  authorization: 'CustomTokenValue',
  authorizationType: 'Custom'
});

Resilience Features

RestFit includes built-in resilience features similar to Refit in .NET, with automatic retry policies and circuit breaker patterns enabled by default.

Default Resilience Policy

By default, RestFit applies a resilience policy that includes:

  • Retry Policy: 3 retries with exponential backoff (100ms initial, max 2000ms)
  • Circuit Breaker: Opens after 5 failures in 1 minute, resets after 30 seconds
  • Retries on: 408, 429, 500, 502, 503, 504 status codes and network errors

Using Default Resilience

// Default resilience is automatically applied
const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com'
});

Customizing Resilience

import { createApiService, DEFAULT_RESILIENCE_POLICY } from 'restfit';

// Custom resilience configuration
const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com',
  resilience: {
    retry: {
      retries: 5,
      retryDelay: 200,
      retryDelayMax: 5000,
      exponentialBackoff: true,
      retryableStatusCodes: [429, 500, 502, 503, 504],
      retryOnNetworkError: true
    },
    circuitBreaker: {
      enabled: true,
      threshold: 10,
      window: 60000,
      timeout: 60000,
      minimumRequests: 5,
      errorStatusCodes: [500, 502, 503, 504]
    }
  }
});

Disabling Resilience

// Disable all resilience features
const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com',
  resilience: false
});

// Disable only retry
const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com',
  resilience: {
    retry: false,
    circuitBreaker: {
      // custom circuit breaker config
    }
  }
});

Advanced Resilience Configuration

// Custom retry logic
const userService = createApiService(UserService, {
  baseUrl: 'https://api.example.com',
  resilience: {
    retry: {
      retries: 3,
      shouldRetry: async (error, retryCount) => {
        // Custom logic to determine if request should be retried
        if (retryCount >= 3) return false;
        if (error.response?.status === 429) return true;
        return false;
      },
      retryDelayFn: (retryCount, error) => {
        // Custom delay calculation
        if (error.response?.status === 429) {
          return error.response.headers['retry-after'] || 1000;
        }
        return 100 * Math.pow(2, retryCount);
      }
    },
    circuitBreaker: {
      isFailure: (error) => {
        // Custom failure detection
        return error.response?.status >= 500 || !error.response;
      }
    }
  }
});

Resilience Policy Options

RetryPolicy

  • retries (number): Number of retry attempts (default: 3)
  • retryDelay (number): Initial delay in milliseconds (default: 100)
  • retryDelayMax (number): Maximum delay in milliseconds (default: 2000)
  • exponentialBackoff (boolean | number): Enable exponential backoff with optional multiplier (default: true)
  • retryableStatusCodes (number[]): HTTP status codes that trigger retry (default: [408, 429, 500, 502, 503, 504])
  • retryOnNetworkError (boolean): Retry on network errors (default: true)
  • shouldRetry (function): Custom function to determine if request should be retried
  • retryDelayFn (function): Custom function to calculate retry delay

CircuitBreakerPolicy

  • enabled (boolean): Enable circuit breaker (default: true)
  • threshold (number): Failure threshold before opening circuit (default: 5)
  • window (number): Time window in milliseconds to count failures (default: 60000)
  • timeout (number): Time in milliseconds before attempting to close circuit (default: 30000)
  • minimumRequests (number): Minimum requests in window before circuit can open (default: 10)
  • errorStatusCodes (number[]): HTTP status codes that count as failures (default: [500, 502, 503, 504])
  • isFailure (function): Custom function to determine if an error counts as a failure

Credits

RestFit is inspired by:

  • Retrofit - A type-safe HTTP client for Android and Java by Square
  • Refit - The automatic type-safe REST library for .NET by ReactiveUI

We thank the creators and maintainers of these excellent libraries for their inspiration and design patterns.

License

MIT