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

zoxios

v0.1.12

Published

A chainable validated HTTP request maker

Downloads

1,935

Readme

Features

  • Parsing HTTP requests' body and query parameters and responses' body using Zod
  • Chainable API.
  • Make HTTP requests using axios

installation

Install peer dependencies

npm i axios zod

Install zoxios

npm i zoxios

Basic usage

Parsing request's query and response's body.

import { zoxios } from 'zoxios';

// GET http://hostname/api/orders?page=1&limit=10
const response = await zoxios('http://hostname/api/orders')
    .method('GET')
    .querySchema(z.object({ page: z.number(), limit: z.number() }))
    .query({ page: 1, limit: 10 }) // { page: number; limit: number; }
    .responseSchema(z.array(z.object({ id: z.number() })))
    .exec();

// response: { id: number; }[]

console.log(response);
// [ { id: 1 }, { id: 2 } ]

Without Parsing

Requests can be made without defining parsing schemas for the request and the response.

// GET http://hostname/api/orders?page=1&limit=10
const response = await zoxios('http://hostname/api/orders')
    .method('GET')
    .query({ page: 1, limit: 10 }) // unknown
    .exec();

// response: unknown

console.log(response);
// [ { id: 1 }, { id: 2 } ]

Reusing and adding on-top of the request-maker

The request-maker is chainable, therefore it can help minimize code repetition by defining reusable "api layers".

Example

Every request maker built on top on this one will have the same host, headers and the /api path

function getBaseRequestMaker() {
    const options = { headers: { Authorization: `Bearer token` } };

    return zoxios('http://hostname')
        .options(options)
        .concatPath('api');
}

Every request maker built on top of this one will include the settings from getBaseRequestMaker and the /orders path

function buildOrdersRequestMaker() {
    return getBaseRequestMaker().concatPath('orders');
}

This function will make a request including all of the settings from buildOrdersRequestMaker and:

  • set the HTTP method as POST
  • parse the specified body with the provided bodySchema.
  • parse the response with the provided responseSchema
function createOrder(itemId: string, amount: number) {
    return buildOrdersRequestMaker()
        .method('post')
        .bodySchema(z.object({ itemId: z.string(), amount: z.number() }))
        .responseSchema(z.object({ id: z.number() }))
        .body({ itemId, amount })
        .exec(); // Promise<{ id: number }>
}

Every request maker built on top on this one will include the settings from buildOrdersRequestMaker and the settings from this maker:

  • A GET HTTP method
  • A response schema definition
function buildGetOrdersRequestMaker() {
    return buildOrdersRequestMaker()
        .method('get')
        .responseSchema(z.array(z.object({ id: z.number() })));
}

This function will make a request including all of the settings from buildGetOrdersRequestMaker and parse the specified query with the provided querySchema.

function getOrdersPaginated(page: number, limit: number) {
    return buildGetOrdersRequestMaker()
        .querySchema(z.object({ page: z.number(), limit: z.number() }))
        .query({ page, limit }) // { page: number; limit: number; }
        .exec(); // Promise<{ id: number }[]
}

This function will make a request including all of the settings from buildGetOrdersRequestMaker and parse the specified query with the provided querySchema.

function getOrdersInDateRange(startDate: Date, endDate: Date) {
    return buildGetOrdersRequestMaker()
        .querySchema(z.object({ startDate: z.date(), endDate: z.date() }))
        .query({ startDate, endDate }) // { startDate: Date; endDate: Date; }
        .exec(); // Promise<{ id: number }[]
}

.options

Set axios options. Every options set here will be overridden if set again in later chains, by calling options or other method resetting the value you defined here.

zoxios('http://hostname').options({ timeout: 1000 });

.asyncOptionsSetter

Set axios options asynchronously. Provide an async function that will return axios options object. This async function will run before each request and set the options. Other set options will have priority over this one (only relevant when same option property is set).

Can be useful when each request have to calculate a request-signature or a token asynchronously.

zoxios('http://hostname')
    .asyncOptionsSetter(async () => ({ headers: { Authorization: await Promise.resolve('token') } }))

.concatPath

Will concat to the path defined up until its usage. Each concatPath adds a "/" before the provided value.

zoxios('http://hostname')
    .concatPath('api') // current url - http://hostname/api
    .concatPath('users') // current url - http://hostname/api/users
    .concatPath(5); // current url - http://hostname/api/users/5

This example will create the following URL: http://hostname/api/users/5

.getDefinition

Will return the definition of the request-maker which will include the hostname, querySchema, bodySchema, responseSchema, query, body, path, options and method.

const body = { name: 'n', age: 1 };
const responseSchema = z.object({ id: z.number() });
const query = { endDate: new Date(), startDate: new Date() };
const bodySchema = z.object({ name: z.string(), age: z.number() });
const querySchema = z.object({ startDate: z.date(), endDate: z.date() });

const requestMaker = zoxios('localhost')
    .concatPath('api')
    .concatPath('orders')
    .querySchema(querySchema)
    .bodySchema(bodySchema)
    .responseSchema(responseSchema)
    .body(body)
    .query(query);

const definition = requestMaker.getDefinition();

// definition.body = body;
// definition.query = query;
// definition.path = '/api/orders';
// definition.hostname = 'localhost';
// definition.bodySchema = bodySchema;
// definition.querySchema = querySchema;
// definition.responseSchema = responseSchema;

.host

Will change the host of the request-maker.

const requestMaker = zoxios('https://localhost-1').concatPath('api').method('get');

// GET https://localhost-1/api
const validatedRequestResponse1 = await requestMaker.exec();

// GET https://localhost-2/api
const validatedRequestResponse2 = await requestMaker.host('https://localhost-2').exec();

.handleHttpError

Will add an http error handler that will handle errors thrown by axios, you should check if the provided error is an axios error (using isAxiosError) if you wish to handle those explicitly.

The error-handlers you define will ran one after the other: each one will pass the error to the next error-handler in-line, if an error exists, otherwise it will resolve the promise and ignore the rest of the error-handlers.

example for one error handler:

// instead of throwing an error, null will be returned.
await zoxios('https://localhost')
    .concatPath('api')
    .method('get')
    .handleHttpError((error) => {
        return null;
    })
    .exec()

example for multiple error handlers:

// instead of the original error, the new error created will be thrown.
await zoxios('https://localhost')
    .concatPath('api')
    .method('get')
    .handleHttpError((error) => {
        console.log(error);
        throw error;
    })
    .handleHttpError(() => {
        throw new Error('other error');
    })
    .exec()