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 🙏

© 2024 – Pkg Stats / Ryan Hefner

retroxios

v0.4.4

Published

A type-safe HTTP client for Node.js and browsers.

Downloads

47

Readme

Retroxios allows you to easily construct declarative yet flexible HTTP clients (powered by axios) for API services, by using decorators and leveraging the type system of TypeScript at the same time.

It automagically turns all your decorated methods into axios HTTP request calls against your API service.

You can think of this as a TypeScript port of Square's Retrofit.

By using decorators, a large amount of repetitious code can be reduced. Less code, fewer bugs, more power.

Features

  • ✍🏻 More declarative code
  • 🤖 Less repetitious code (DRY)
  • 👨🏻‍💻 Better developer experience
  • 🦆 Make good use of TypeScript's type system

Overview

  1. Define a class containing methods (as endpoints) of the API service
class ExampleService {
  @GET("users/{name}", {
    queries: { page: 1 }, // (default queries)
    headers: { foo: "bar" }, // (default headers)
  })
  public async getUser(
    @Path("name") name: string, // replaces '{name}' in the endpoint
    @Query("page") page: number = 1, // add 'page=1' to queries
    @Header("foo") foo: string = "bar", // add [foo: bar] to headers
    @QuerySpread filters?: Filters // entries of this argument become key-value queries
  ): Promise<AxiosResponse<UserResponse>> {
    // No body for this method as it will transform into an axios request call.
    return nothing(name, page, foo, filters); // put all unused parameters into this function
  }
}
  1. Create an instance of ExampleService via Retroxios
// Construct a Retroxios object with a base config.
const baseURL = "https://api.example.com/";
const builder = new Retroxios({ baseURL, headers: { Accept: "application/json" } });

// Build an ExampleService instance.
const service = builder.create(ExampleService);

// You can additionally extend the base config to
// have multiple sub services deriving from a parent config.
const subService = builder.extend({ baseURL: `${baseURL}/sub/` }).create(ExampleSubService);
  1. Call the HTTP requests as you normally do
// GET https://api.example.com/users/john?page=3&gender=M
// with headers: [Accept: "application/json"] [foo: "bar"]
const call = service.getUser("john", 3, undefined, { gender: "M" });
call.then(({ status, data }) => console.log(status, data)).catch(console.error);

Download

$ npm install retroxios

Usage

More examples here.


import Retroxios from "retroxios";
import * as decorators from "retroxios/decorators";

Request Decorators

Support HTTP GET, HEAD, POST, PUT, DELETE, OPTIONS and PATCH methods.

The relative endpoint of the URL is specified as the first parameter in the decorator.

@GET("users/list")

The second parameter (defaults) receives optional default queries and headers that will be added to the request if there is no corresponding parameters or when they are undefined.

@GET("users/list", {
  queries: { page: 1, limit: 10 },
  headers: { Accept: "application/json" }
})

Note that these are method decorators and must be placed above the @Config() method decorator (if any). Also, these decorators should not be attached to the same method more than once.

The methods attached with these decorators should be an async function that returns Promise<AxiosResponse<T>> where T is the object type of the expected JSON response. For example:

async getUser(): Promise<AxiosResponse<UserResponse>>

Path Manipulation

The path of a request endpoint can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric (_ is also allowed) key surrounded by { and }. A corresponding parameter must be decorated with @Path("key") using the same key.

@GET("group/{id}/users")
async groupList(@Path("id") groupID: number): Promise<AxiosResponse> {
  return nothing(groupID);
};

You can additionally specify a default value for the path to use when the corresponding parameter is undefined, by using the syntax {key=(default)}, where the default value is surrounded by ( and ) following a =.

@GET("group/{id=(999)}/users")
async groupList(@Path("id") groupID = 999): Promise<AxiosResponse> {
  return nothing(groupID);
};

Query Manipulation

Queries can be added using the @Query("key") parameter decorator like this:

@GET("group/{id}/users")
async groupList(
  @Path("id") groupID: number,
  @Query("sort") sort: string
): Promise<AxiosResponse> {
  return nothing(groupID, sort);
};

For complex query combinations, the @QuerySpread parameter decorator can be used.

@GET("group/{id}/users")
async groupList(
  @Path("id") groupID: number,
  @QuerySpread options: Options
): Promise<AxiosResponse> {
  return nothing(groupID, options);
};

Header Manipulation

Headers can be added using the @Header("key") parameter decorator like this:

@GET("group")
async getGroup(@Header("Authorization") authorization: string): Promise<AxiosResponse> {
  return nothing(authorization);
};

For complex query combinations, the @HeaderSpread parameter decorator can be used.

@GET("group")
async getGroup(@HeaderSpread headers: GroupHeaders): Promise<AxiosResponse> {
  return nothing(headers);
};

Request Body

You can supply a data body to the request using the @Body parameter decorator like this:

@POST("users/new")
async createUser(@Body user: UserEntity): Promise<AxiosResponse> {
  return nothing(user);
};

Note that this decorator is only allowed when using the POST, PUT and PATCH request decorators.

Per Request Config

You can supply an additional config (AxiosRequestConfig) for a particular request like this:

@GET("user/posts")
@Config({ ... })
async getUserPosts(): Promise<AxiosResponse> {
  return nothing();
};

Some options such as url, params, headers and data may get overridden by decorated parameters.

Note that this decorator should only be attached once per method and should not be used when defaults arguments are supplied to the reqeust decorator at the same time.


Response Manipulator

The @Manipulate() method decorator allows you to manipulate the response or even change the return type of the decorated request method (such as wrap the response in another object etc.).

@GET("user/posts")
@Manipulate((response: AxiosResponse): string => response.statusText})
async getUserPosts(): Promise<string> {
  return nothing();
};

Note that this decorator should only be attached once per method.

Difference with Response Interceptor

export type Interceptor<AxiosResponse> = {
  onFulfilled?: (value: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
  onRejected?: (error: any) => any;
};

As you can see, onFulfilled requires you to return an AxiosResponse (or its promised variant). This means you cannot control what you want to get freely. Having said that, interceptors are quite useful in some other cases.

Difference with AxiosTransformer

export interface AxiosTransformer {
  (data: any, headers?: any): any;
}
// Declaration from 'axios/index.d.ts'

As you may already noticed, AxiosTransformer does not receive the whole response as the parameter, instead it receives only the data and the headers of the response. Therefore, its capability is limited comapred with @Manipulate().


Returning nothing()

The nothing(..._args: any[]) function returns null as any. It accepts anything as arguments.

All methods decorated with any request decorator are recommended to return nothing().

Since these methods do not have a body and have nothing to return, linters such as eslint would complain about returning void cannot assign to the method's return type Promise<AxiosResponse<T>>. Therefore, returning nothing() can eliminate these warnings.

Moreover, due to the same reason, the parameters passed to the decorated method are unused. Therefore, you can feed these unused arguments into nothing(...args) in order to eliminate no-unused-vars warnings.


Builder Configurations

Learn more about the config (AxiosRequestConfig) and the interceptors here.

The Retroxios object acts as a builder to construct service instances based on a given config. The service instances it creates will then inherit its config.

const baseConfig: AxiosRequestConfig = { ... };
const baseBuilder = new Retroxios(baseConfig);

const baseService = baseBuilder.create(ExampleBaseService);

Note that some options in the config (such as url, params, headers and data) supplied to the Retroxios object may get overridden by the same options specified in the request decorators.

Additionally, you can supply interceptors like this:

baseBuilder.interceptors.request.use(
  (config) => {
    console.log(config);
    return config;
  },
  (error) => console.log(error)
);

baseBuilder.interceptors.response.use(
  (response) => {
    console.log(response);
    return response;
  },
  (error) => console.log(error)
);

The Retroxios object can be extended with another config to create service instances that derive from a base config.

const subConfig: AxiosRequestConfig = {};
const subBuilder = baseBuilder.extend(subConfig);

const subService = subBuilder.create(ExampleSubService);

Contributing

If you have any ideas on how to improve this project or if you think there is a lack of features, feel free to open an issue, or even better, open a pull request. All contributions are welcome!