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 🙏

© 2026 – Pkg Stats / Ryan Hefner

http-egy

v0.4.7

Published

An Angular HTTP client extension built on top of `HttpClient`. Removes repetitive boilerplate while keeping full compatibility with Angular's native API.

Readme

HttpEgy

An Angular HTTP client extension built on top of HttpClient. Removes repetitive boilerplate while keeping full compatibility with Angular's native API.

Features

  • Clean request configuration with plain objects
  • Plain object headers and query params — no HttpParams/HttpHeaders ceremony
  • Global base URL and default headers
  • Signal-based reactive resources (watch, watchGet, watchMutation)
  • Function-based interceptors with sync and async support
  • RxJS operator support in reactive pipelines
  • Tree-shakable and fully standalone
  • Typed responses and errors
  • Timeout and AbortSignal support
  • Zero NgModules — pure functional providers

Why HttpEgy?

Angular's HttpClient is intentionally low-level. It gives you full control but requires repetitive setup for common tasks:

  • Building HttpParams and HttpHeaders objects
  • Configuring timeouts and base URLs per request
  • Writing boilerplate for loading and error states
  • Manually connecting reactive signals to HTTP calls

HttpEgy builds on top of HttpClient by providing:

  • Config-object-based methods — headers and query params are plain objects; the library converts them internally
  • Reactive resources — signal-based handles that track value, isLoading, error automatically
  • Global configuration — set base URL, default headers, default options, and interceptors once
  • Function-based interceptors — simpler than Angular's handler-chain pattern

Every method returns a standard RxJS Observable, so you can use pipe(), toSignal(), toObservable(), or any RxJS operator as usual.

Installation

npm install http-egy

Configuration

Call provideHttp():

import { provideHttp } from 'http-egy';

bootstrapApplication(App, {
  providers: [
    provideHttp({
      baseUrl: 'https://api.example.com',
      defaultHeaders: {
        'Content-Type': 'application/json',
      },
      defaultOptions: {
        withCredentials: true,
        timeout: 10_000,
      },
    }),
  ],
});

| Option | Type | Default | Description | |---|---|---|---| | baseUrl | string | '' | Prepended to all request URLs | | defaultHeaders | Record<string, string \| string[]> | {} | Default headers for every request | | defaultOptions | RequestOptions | {} | Default timeout, withCredentials, etc. | | interceptors | HttpInterceptorFn[] | [] | Library interceptor functions | | angularInterceptors | Angular HttpInterceptorFn[] | [] | Angular HttpClient interceptors |

Basic Usage

injectHttp

import { injectHttp } from 'http-egy';

@Component({...})
export class UsersComponent {
  private http = injectHttp();
}

Basic requests

this.http.get<User[]>('/api/users');
this.http.post<User>('/api/users', { body: { name: 'John' } });
this.http.put<User>('/api/users/1', { body: { name: 'John Updated' } });
this.http.patch<User>('/api/users/1', { body: { name: 'John Patched' } });
this.http.delete<void>('/api/users/1');

Query parameters

this.http.get<User[]>('/api/users', {
  query: {
    page: 1,
    limit: 20,
    roles: ['admin', 'editor'],
  },
});

Headers

this.http.get<User>('/api/users/me', {
  headers: {
    Authorization: `Bearer ${token}`,
    'X-Request-ID': crypto.randomUUID(),
  },
});

Response types

// Observe body (default)
this.http.get<User[]>('/api/users');

// Observe full response
this.http.get('/api/users', {
  options: { observe: 'response' },
});

// Observe events (progress)
this.http.get('/api/large-file', {
  options: { observe: 'events', reportProgress: true, responseType: 'blob' },
});

Timeout

this.http.get('/api/users', {
  options: { timeout: 5_000 },
});

Cancellation

const controller = new AbortController();

this.http.get('/api/users', {
  options: { signal: controller.signal },
});

controller.abort();

Reactive Resources

Reactive resources return an HttpResource<T, E> — a signal-based handle that tracks the full lifecycle of a request:

  • value — last successful response body (Signal<T | undefined>)
  • isLoading — true while request is in flight (Signal<boolean>)
  • error — last error, if any (Signal<E | undefined>)
  • hasValue() — true if value() is not undefined
  • reload(data?) — re-run the request

watch() — reactive reads

Re-runs the request automatically whenever any signal read inside source() changes.

const postId = signal(1);

const post = this.http.watch<Post>({
  source: () => ({
    method: 'GET',
    url: `/posts/${postId()}`,
  }),
});

// post.value()      — Signal<T | undefined>
// post.isLoading()  — Signal<boolean>
// post.error()      — Signal<HttpErrorResponse | undefined>

watchGet() — reactive read shorthand

const posts = this.http.watchGet<Post[]>({
  source: () => `/posts?page=${page()}`,
});

watchMutation() — explicit-trigger only

Never tracks signals. Changing a signal inside source() will not start a request. The request executes only when reload() is called.

readonly loginResult = this.http.watchMutation<LoginResponse>({
  source: () => ({
    method: 'POST',
    url: '/api/login',
    body: this.loginForm.value,
  }),
});

login() {
  this.loginResult.reload();
}

reload(data?)

Forwards data to the source() function so it can adjust the request config:

readonly users = this.http.watch<User[]>({
  source: (data?: { page?: number }) => ({
    method: 'GET',
    url: '/api/users',
    query: { page: data?.page ?? 1 },
  }),
});

this.users.reload({ page: 2 });
this.users.reload(); // source receives undefined

RxJS operators

Both watch() and watchMutation() accept an operators function:

const users = this.http.watch<User[]>({
  source: () => ({ method: 'GET', url: '/api/users' }),
  operators: (obs$) => obs$.pipe(
    tap(list => console.log('Loaded:', list.length)),
    map(list => list.filter(u => u.active)),
  ),
});

Interceptors

Library interceptors are functions that receive a RequestConfig and return a modified one. They can be synchronous or asynchronous.

import type { HttpInterceptorFn } from 'http-egy';

// Synchronous
const loggingInterceptor: HttpInterceptorFn = (config) => {
  console.log(`[HTTP] ${config.method} ${config.url}`);
  return config;
};

// Async (Observable)
const tokenInterceptor: HttpInterceptorFn = (config) =>
  authService.refreshToken().pipe(
    map(token => ({
      ...config,
      headers: { ...config.headers, Authorization: `Bearer ${token}` },
    })),
  );

// Async (Promise)
const promiseInterceptor: HttpInterceptorFn = async (config) => {
  const token = await authService.getToken();
  return { ...config, headers: { ...config.headers, Authorization: `Bearer ${token}` } };
};

Registration:

provideHttp({ interceptors: [loggingInterceptor, tokenInterceptor] })

Angular interceptors can also be used alongside library interceptors:

provideHttp({
  angularInterceptors: [myAngularInterceptor],
})

Error Handling

The error type E defaults to HttpErrorResponse. Override it when your interceptor normalizes errors:

interface ApiError { message: string; code: string; }

const login = this.http.watchMutation<LoginResponse, ApiError>({
  source: () => ({ method: 'POST', url: '/api/login', body: this.loginForm.value }),
});

if (login.error()) {
  console.log(login.error()?.message); // typed as ApiError
}

Without custom types, access the standard HttpErrorResponse fields:

this.http.get<User[]>('/api/users').pipe(
  catchError((error: HttpErrorResponse) => {
    if (error.status === 401) {
      // Redirect to login
    }
    return throwError(() => error);
  }),
);

Why not HttpClient directly?

| Concern | HttpClient | HttpEgy | |---|---|---| | Headers | new HttpHeaders({...}) | Plain object | | Query params | new HttpParams({...}) | Plain object | | Base URL | Manual concatenation | baseUrl in config | | Default headers | Manual per request | defaultHeaders in config | | Timeout | Manual AbortSignal | timeout option | | Interceptors | Class-based or complex chain | Function-based | | Reactive resources | Manual toSignal + switchMap | Built-in watch() / watchMutation() | | Loading/error state | Manual signals | Built-in isLoading / error | | Boilerplate | High (params, headers, options per call) | Low (config object) |

HttpEgy builds on top of HttpClient without replacing it. Every method returns a standard RxJS Observable, and you can still use Angular interceptors alongside library interceptors.

Architecture

RequestConfig (method, url, headers, query, body, options)
      │
      ▼
Library interceptors (modify config — sync or async)
      │
      ▼
Defaults merge (baseUrl, defaultHeaders, defaultOptions)
      │
      ▼
Headers & query conversion (plain objects → HttpHeaders / HttpParams)
      │
      ▼
Angular HttpClient (request execution)
      │
      ▼
Response mapping (body / response / events)
      │
      ▼
Observable<T> / HttpResource<T, E>

API Reference

provideHttp(config?)

| Option | Type | Default | Description | |---|---|---|---| | baseUrl | string | '' | Prepended to all request URLs | | defaultHeaders | Record<string, string \| string[]> | {} | Default headers for every request | | defaultOptions | RequestOptions | {} | Default request options | | interceptors | HttpInterceptorFn[] | [] | Library interceptor functions | | angularInterceptors | Angular HttpInterceptorFn[] | [] | Angular HttpClient interceptors |

injectHttp()

Returns the singleton HttpService instance. Must be called within an injection context.

HttpService

| Method | Returns | Description | |---|---|---| | request<T>(config) | Observable<T> | Full request with RequestConfig | | get<T>(url, config?) | Observable<T> | GET request | | post<T>(url, config?) | Observable<T> | POST request | | put<T>(url, config?) | Observable<T> | PUT request | | patch<T>(url, config?) | Observable<T> | PATCH request | | delete<T>(url, config?) | Observable<T> | DELETE request | | watch<T, E>(config) | HttpResource<T, E> | Reactive read — tracks signals | | watchGet<T, E>(config) | HttpResource<T, E> | Reactive read shorthand (GET) | | watchMutation<T, E>(config) | HttpResource<T, E> | Explicit-trigger mutation |

HttpResource<T, E>

| Member | Type | Description | |---|---|---| | value | Signal<T \| undefined> | Last successful response body | | isLoading | Signal<boolean> | True while a request is in flight | | error | Signal<E \| undefined> | Last error (default HttpErrorResponse) | | hasValue() | () => boolean | True if value() is not undefined | | reload(data?) | (data?: unknown) => void | Re-runs the request; data forwarded to source |

RequestConfig

| Field | Type | Description | |---|---|---| | method | 'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE' | HTTP method | | url | string | Request URL (relative or absolute) | | headers | Record<string, string> | HTTP headers | | query | Record<string, string \| string[]> | Query parameters | | body | unknown | Request body | | options | RequestOptions | Angular HttpClient options |

RequestOptions

| Option | Type | Default | Description | |---|---|---|---| | observe | 'body' \| 'response' \| 'events' | 'body' | What the Observable emits | | responseType | 'json' \| 'text' \| 'blob' \| 'arraybuffer' | 'json' | Expected response format | | reportProgress | boolean | false | Emit progress events | | withCredentials | boolean | false | Send cookies with cross-origin requests | | timeout | number | 0 | Max time in ms before abort | | signal | AbortSignal | — | Manual cancellation signal |

HttpResponse<T>

Returned when observe: 'response' is used.

| Field | Type | Description | |---|---|---| | data | T | Response body | | status | number | HTTP status code | | statusText | string | HTTP status text | | headers | Record<string, string> | Response headers | | url | string | Response URL |

License

MIT