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

@rest-vir/api

v2.2.0

Published

Define a declarative and type safe REST API and connect to it from a client.

Downloads

2,764

Readme

@rest-vir/api

@rest-vir/api is the shared definition, typed client, and client-side testing package for rest-vir.

Use it to:

  • define endpoint and WebSocket contracts with runtime shapes
  • export one API definition that frontend and backend code can share
  • call endpoints with RestVirClient
  • connect to typed WebSockets
  • create in-memory mock hosts for tests

See the full reference docs at https://electrovir.github.io/rest-vir.

For a working package example, see packages/demo on GitHub.

Install

npm i @rest-vir/api object-shape-tester

object-shape-tester is required for request, response, search param, header, and WebSocket message shapes.

Define An API

import {defineApi, defineEndpoint, HttpMethod, HttpStatus} from '@rest-vir/api';
import {defineShape} from 'object-shape-tester';

export const healthEndpoint = defineEndpoint({
    path: '/health',
    requests: {
        [HttpMethod.Get]: {
            responses: {
                [HttpStatus.Ok]: {
                    responseData: defineShape({
                        status: '',
                    }),
                },
            },
        },
    },
});

export const myApi = defineApi({
    apiName: 'my-api',
    endpoints: [
        healthEndpoint,
    ],
    webSockets: [],
});

Put this file in a shared package or shared source folder. The host imports it to implement the API, and clients import it to call the API.

For large APIs, batch related endpoint definitions together in files. TypeScript type checking is faster when many endpoint definitions are grouped in a few files instead of split into one endpoint per file.

Endpoint Definition Notes

  • path supports Fastify-style path params such as /users/:userId.
  • requestData may be a shape, undefined, or omitted.
  • Omitting requestData disables body validation for that method.
  • Setting requestData: undefined means the endpoint must not receive request body data.
  • responses declares the statuses the client should expect and validate.
  • Undeclared error statuses are returned to the client as unexpectedError.
  • Undeclared success statuses throw, because the client cannot safely type them.
  • searchParams and requiredRequestHeaders can be declared on each method.
  • formDataShape() is available for multipart form uploads.

Typed Fetch Client

import {defineApi, defineEndpoint, HttpMethod, HttpStatus, RestVirClient} from '@rest-vir/api';
import {defineShape} from 'object-shape-tester';

const healthEndpoint = defineEndpoint({
    path: '/health',
    requests: {
        [HttpMethod.Get]: {
            responses: {
                [HttpStatus.Ok]: {
                    responseData: defineShape({
                        status: '',
                    }),
                },
            },
        },
    },
});

const myApi = defineApi({
    apiName: 'my-api',
    endpoints: [
        healthEndpoint,
    ],
    webSockets: [],
});

const client = new RestVirClient(myApi, 'https://api.example.com');

const result = await client.fetch(healthEndpoint).GET();

if (result.Ok) {
    console.info(result.Ok.responseData);
}

RestVirClient builds URLs, validates params, applies required headers, serializes JSON request bodies, parses JSON responses with JSON content types, and validates declared response shapes.

For Server-Sent Events or other streaming responses, use client.fetchStream(endpoint, method, params) to receive a ReadableStream instead of parsed response data.

WebSockets

import {defineApi, defineWebSocket, RestVirClient} from '@rest-vir/api';
import {defineShape} from 'object-shape-tester';

export const chatWebSocket = defineWebSocket({
    path: '/ws/chat',
    clientMessage: defineShape({
        text: '',
    }),
    hostMessage: defineShape({
        text: '',
        sender: '',
    }),
});

export const chatApi = defineApi({
    apiName: 'chat-api',
    endpoints: [],
    webSockets: [
        chatWebSocket,
    ],
});

const client = new RestVirClient(chatApi, 'https://api.example.com');

const webSocket = await client.connectWebSocket(chatWebSocket, {
    listeners: {
        message({message}) {
            console.info(message.sender, message.text);
        },
    },
});

webSocket.send({
    text: 'Hello.',
});

WebSocket definitions may also declare search params and connection protocol requirements.

Mocking A Host In Tests

import {createMockHost, defineApi, defineEndpoint, HttpMethod, HttpStatus} from '@rest-vir/api';
import {defineShape} from 'object-shape-tester';

const healthEndpoint = defineEndpoint({
    path: '/health',
    requests: {
        [HttpMethod.Get]: {
            responses: {
                [HttpStatus.Ok]: {
                    responseData: defineShape({
                        status: '',
                    }),
                },
            },
        },
    },
});

const myApi = defineApi({
    apiName: 'my-api',
    endpoints: [
        healthEndpoint,
    ],
    webSockets: [],
});

export const mockClient = createMockHost(myApi, {
    endpoints: {
        '/health': {
            [HttpMethod.Get]: () => ({
                [HttpStatus.Ok]: {
                    responseData: {
                        status: 'ok',
                    },
                },
            }),
        },
    },
});

The mock host returns a real RestVirClient, so application code can use the same client calls in tests that it uses in production.

Pair With @rest-vir/host

Use this package for the shared contract and client calls. Use @rest-vir/host in the server process to implement the same definition:

import {defineApi, defineEndpoint, HttpMethod, HttpStatus} from '@rest-vir/api';
import {createApiImplementor, implementApi} from '@rest-vir/host';
import {defineShape} from 'object-shape-tester';

const healthEndpoint = defineEndpoint({
    path: '/health',
    requests: {
        [HttpMethod.Get]: {
            responses: {
                [HttpStatus.Ok]: {
                    responseData: defineShape({
                        status: '',
                    }),
                },
            },
        },
    },
});

const myApi = defineApi({
    apiName: 'my-api',
    endpoints: [
        healthEndpoint,
    ],
    webSockets: [],
});

const implementor = createApiImplementor<undefined>()(myApi);

export const myApiImplementation = implementApi<undefined>()(myApi, {
    createHostContext() {
        return {
            context: undefined,
        };
    },
    endpoints: [
        implementor.implementEndpoint(healthEndpoint, {
            [HttpMethod.Get]() {
                return {
                    [HttpStatus.Ok]: {
                        responseData: {
                            status: 'ok',
                        },
                    },
                };
            },
        }),
    ],
});