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/host

v2.2.0

Published

Implement a declarative and type safe REST API and host it.

Readme

@rest-vir/host

@rest-vir/host is the Fastify runtime for an API defined with @rest-vir/api.

Use it to:

  • implement typed endpoint methods and WebSocket listeners
  • create per-request host context
  • validate request bodies, search params, headers, and WebSocket messages
  • run a new Fastify server or attach routes to an existing one
  • test implementations with real request and response objects

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/host @rest-vir/api object-shape-tester

@rest-vir/api provides the shared API definition. object-shape-tester provides the runtime shapes used by that definition.

Implement And Run An API

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

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

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

const {implementEndpoint} = createApiImplementor<undefined>()(myApi);

export const healthImplementation = implementEndpoint(healthEndpoint, {
    [HttpMethod.Get]() {
        return {
            [HttpStatus.Ok]: {
                responseData: {
                    status: 'ok',
                },
            },
        };
    },
});

export const apiImplementation = implementApi<undefined>()(myApi, {
    createHostContext: () => ({
        context: undefined,
    }),
    endpoints: [healthImplementation],
});

const {kill} = await startApiServer(apiImplementation, {
    port: 3000,
    externalOrigin: 'http://localhost:3000',
});

// later, to shut down:
await kill();

implementApi accepts arrays of implementations. It validates that every endpoint and WebSocket declared by the API definition has exactly one matching implementation.

Host Context

createHostContext runs before endpoint and WebSocket handlers. Return {context} to pass typed context into every implementation callback.

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({
                        requestId: '',
                    }),
                },
            },
        },
    },
});

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

type HostContext = {
    requestId: string;
};

const {implementEndpoint} = createApiImplementor<HostContext>()(myApi);

const healthImplementation = implementEndpoint(healthEndpoint, {
    [HttpMethod.Get]({context}) {
        return {
            [HttpStatus.Ok]: {
                responseData: {
                    requestId: context.requestId,
                },
            },
        };
    },
});

export const apiImplementation = implementApi<HostContext>()(myApi, {
    createHostContext() {
        return {
            context: {
                requestId: crypto.randomUUID(),
            },
        };
    },
    endpoints: [
        healthImplementation,
    ],
});

createHostContext can also reject a request by returning {reject} with a status code, response data, and optional headers.

WebSocket Implementations

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

const echoWebSocket = defineWebSocket({
    path: '/ws/echo',
    clientMessage: defineShape({
        value: '',
    }),
    hostMessage: defineShape({
        value: '',
    }),
});

const api = defineApi({
    apiName: 'socket-api',
    endpoints: [],
    webSockets: [
        echoWebSocket,
    ],
});

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

const echoImplementation = implementor.implementWebSocket(echoWebSocket, {
    message({message, webSocket}) {
        webSocket.send({
            value: message.value,
        });
    },
});

export const apiImplementation = implementApi<undefined>()(api, {
    createHostContext() {
        return {
            context: undefined,
        };
    },
    webSockets: [
        echoImplementation,
    ],
});

WebSocket listeners may implement open, message, and close. Incoming client messages are validated before message runs.

Attach To An Existing Fastify Server

import {attachApi} from '@rest-vir/host';
import fastify from 'fastify';
import {apiImplementation} from './basic-api-implementation.example.js';

const server = fastify();

await attachApi(server, apiImplementation, {
    externalOrigin: 'http://localhost:3000',
});

await server.listen({
    port: 3000,
});

attachApi registers compression and WebSocket support automatically. Multipart support is registered only when an endpoint uses formDataShape().

CORS And Origins

If no clientOriginRequirement is set on a route or on implementApi, all origins are allowed. Set clientOriginRequirement for production APIs.

Accepted origin requirements come from @rest-vir/api:

  • exact origin string
  • RegExp
  • callback returning true or false
  • AnyOrigin
  • {anyOrigin: true}
  • {anyOriginWithCredentials: true}

Per-route requirements override the API-level requirement.

Runtime Options

startApiServer(api, options) requires externalOrigin, which is the public origin clients use to reach the API. Common options include:

  • port: listen port, defaulting to 3000
  • host: listen host, defaulting to localhost
  • workerCount: number of worker processes, defaulting to CPU count minus one
  • bodyLimit: maximum HTTP request body size
  • webSocketMaxPayload: maximum inbound WebSocket message size
  • connectionTimeout, keepAliveTimeout, and requestTimeout
  • trustProxy: Fastify proxy trust configuration

startApiServer serves plaintext HTTP. Terminate TLS in a reverse proxy or configure your own Fastify instance and use attachApi.

Testing

Use testApi for integration tests without manually managing a server:

import {HttpMethod} from '@rest-vir/api';
import {condenseResponse, testApi} from '@rest-vir/host';
import {apiImplementation, healthEndpoint} from './basic-api-implementation.example.js';

const {fetchEndpoint, kill} = await testApi(apiImplementation);

const response = await fetchEndpoint(healthEndpoint, HttpMethod.Get);

console.info(await condenseResponse(response));

await kill();

describeEndpoint wraps testEndpoint in table-driven tests:

import {HttpStatus} from '@rest-vir/api';
import {describeEndpoint} from '@rest-vir/host';
import {healthImplementation} from './basic-api-implementation.example.js';

function createHostContext() {
    return {
        context: undefined,
    };
}

describeEndpoint(healthImplementation, ({endpointCases}) => {
    endpointCases.GET({createHostContext}, [
        {
            it: 'responds with ok',
            input: {},
            expect: {
                response: {
                    [HttpStatus.Ok]: {
                        body: {
                            status: 'ok',
                        },
                    },
                },
            },
        },
    ]);
});

testApi uses Fastify request injection by default, so it does not need a real port. Pass a port option when you need full network behavior. describeApi, describeEndpoint, testEndpoint, and testWebSocket are also exported for focused tests.