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

endpoint-tools

v0.4.0

Published

An opinionated but customizable tool for designing, enforcing and communicating rest endpoints. Allowing teams to design API-first applications where the api shape is a library to be imported and consumed like everything else.

Downloads

5

Readme

Endpoint Tools

An opinionated but customizable tool for designing, enforcing and communicating rest endpoints. Allowing teams to design API-first applications where the api shape is a library to be imported and consumed like everything else.

Features

  • Opinionated types for Get, Post, Head, Put, Patch and Delete
  • Meek types for Get, Post, Head, Put, Patch and Delete
  • Mixin types for common properties such as WithId, WithRequestId, WithErrors
  • Tools for parsing and checking endpoints

Concepts

This repo uses a type convention called hierarchical types to gather related types in a single object. A simple example of this concept would be:

type RestGetStates = {
  _request: {
    Params: any;
  };
  _response: {
    Success: any;
    Fail: any;
  };
};

Properties that start with underscore in RestGetStates are hierarchical steps used for grouping related types such as Success and Fail states of _response. While pascal case properties such as Params are types that are intended to be used inside the application.

Usage

Endpoint Tools comes with two variants of each Rest endpoint: Opinionated and 'Meek';

import { Get, GetMeek } from 'endpoint-tools';

Opinionated types impose a certain shape on response types. You can read more about the shape of the opinionated response types here. Meek types let the user define any kind of response shapes for Success and Fail states.

Defining your endpoints with opinionated tools

Assume that we are developing a site that pulls multiple posts of some category from the server using GET. We will demonstrate the use with the opinionated Get tool.

/**
 * An interface that defines all the endpoints related to handling posts
 */
interface CategoryEndpoints {
  _posts: {
    _v1: Get<
      '/category/:categorySlug/posts/v1', // endpoint url
      { categorySlug: string }, // url params
      { start: number; count: number }, // url query
      {
        // shape for the success body
        id: string; // uuid
        title: string;
        content: string;
      }
    >;
  };
}

What we did here is to define a hierarchical interface called CategoryEndpoints that contain all the endpoints related to handling posts. During consumption, we will access _posts/v1 endpoint using index access:

type GetCategoryPostsV1 = CategoryEndpoints['_posts']['_v1'];

Now, let's have a look at what the GetCategoryPostsV1 type hierarchy for our GET endpoint looks like:

{
  // Your endpoint input
  Endpoint: '/category/:categorySlug/posts/v1',
  Type: 'get',  // string literal
  _req: {
    // Your params input
    Params: {
      categorySlug: string,
    },
    // Your query input
    query: {
      start: number,
      count: number,
    }
  },
  _res: { // _silent_snake_case for hierarchy types
    Success: {
      state: 'success', // string literal
      requestId: string,
      // shape for the success response
      body: {
        id: string,
        title: string,
        content: string,
      }
    },
    Fail: { // PascalCase means that this is an independent type
      state: 'fail', // string literal
      requestId: string,
      errors: {
        general: string,
      }
    },
    Union: ... // union of Success and fail, omitting for readability
  }
}

You can see that the created type hierarchy contains params for the request, the Success and Fail types for the response, as well as a Union type that combines the two. For accessing the independent types, you can follow the indexes:

type SinglePost = GetCategoryPostsV1['_res']['Success']['body'];
type RequestParams = GetCategoryPostsV1['_req']['Params'];

Defining your endpoints with meek tools

Meek tools allow you to define custom shapes for your responses. We will design a similar endpoint to the example above.

interface CategoryEndpoints {
  _single: {
    _v1: GetMeek<
      '/category/:categorySlug/posts/v1', // endpoint url
      { categorySlug: string }, // url params
      { start: number; count: number }, // url query
      {
        // shape for the success response (not just body)
        id: string;
        title: string;
        content: string;
      },
      {
        // shape for the error response
        errorCode: string;
      }
    >;
  };
}

Meek tools take two arguments for the response: one for success and one for fail states. The type hierarchy created by the definition above is as follows:

{
  Endpoint: '/category/:categorySlug/posts/v1',
  Type: 'get',
  _req: {
    Params: {
      postSlug: string;
    },
    query: {
      start: number;
      count: number;
    }
  },
  _res: {
    Success: {
      // the success type we provided is used without any additional props
      id: string,
      title: string,
      content: string,
    },
    Fail: {
      // the fail type we provided is used without any additional props
      errorCode: string,
    },
    Union: ... // union of Success and Fail
  }
}

Meek types allow you to use this repo with rest endpoints of all kinds. Note that checking whether the response you got from the server is an error or a success may be inconsistent when you are not relying on a single property like state used in the opinionated variants.

Preparing your endpoints

You will probably have endpoints that require parsing params or a query. To do this safely, you can use the prepareEndpoint function. We will use fetch api to demonstrate the usage.

import { prepareEndpoint, isFail } from 'endpoint-tools';

fetch(
  prepareEndpoint<GetCategoryPostsV1>( // the generic has to be given
    '/category/:categorySlug/posts/v1',
    {
      categorySlug: 'banana-for-scale',
    },
    {
      start: 0,
      count: 20,
    }
  )
) // '/category/banana-for-scale/posts/v1?start=0&count=20'
  .then((response) => response.json())
  .then((data) => {
    // data will be of type GetCategoryPostsV1['_res']['Union']
    // checking for response fail state
    if (isFail(data)) {
      handleError(data);
    } else {
      // here, the type is GetCategoryPostsV1['_res']['Success']
      // do state management
    }

    return data; // GetCategoryPostsV1['_res']['Union']
  });

This will ensure that the endpoint being used matches the endpoint defined in GetCategoryPostsV1. It will also make sure that the endpoint receives the required categorySlug param and have its query parsed as expected.

As defined by our opinionated response, the state property can be used to check for fail states. isFail is some syntactic sugar to check whether data.state === 'fail' holds true while using opinionated tools.

It should be noted that some libraries such as Axios handle parsing of query string, in that case, you may choose to handle query parsing by the tools your preferred library. But using prepareEndpoint will ensure that your query props are checked with a little bit more ease.

Validating your endpoints

On the server-side, you can use validateEndpoint to check your endpoints.

import { validateEndpoint } from 'endpoint-tools';

// IIFE is not required but it enables speedy development as it
// creates a separate scope and allows the use of the same type names.
(() => {
  type Feature = GetCategoryPostsV1;
  type Params = Feature['_req']['Params'];
  // Response has to be Union, not Success
  type Response = Feature['_res']['Union'];
  type Endpoint = Feature['Endpoint'];

  router.get<Params, Response>(
    validateEndpoint<Feature>('/category/:categorySlug/posts/v1'),
    async ({ params: { requestId }, query }, res) => {
      // the rest of your code
    }
  );
})();