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

@pyriter/unrest

v2.8.0

Published

Request routing library for NodeJs. Written for AWS lambda.

Readme

Unrest

Patreon

Description

Request routing for AWS Lambda running Nodejs, written in Typescript

Motivation: Existing routing libraries are inefficient. This library uses a trie data structure with local caching to improve lookup and response time. (More latency data to come)

Install

npm install @pyriter/unrest

Features

  1. Define routes
  2. Define controllers
  3. Type the request body
  4. Support for URL parameters
  5. Query string parameter handling
  6. Request body validation
  7. Response building with status codes
  8. AWS Lambda integration

One Time Setup

Set the "noStrictGenericChecks" to true in your tsconfig to avoid typescript errors

{
  "compilerOptions": {
    ...
    "noStrictGenericChecks": true,
    ...
  }
}

Usage

Basic Setup

import { StatusType, Unrest, MethodType } from "@pyriter/unrest";
import { APIGatewayProxyEvent } from "aws-lambda";
import { UnrestResponse } from "./unrestResponse";
import { RequestProps } from "./route";

class ApiServiceHandler {
  private readonly unrest: Unrest;

  constructor() {
    this.unrest = Unrest.builder()
      .withRoute({
        method: MethodType.GET,
        path: "/api/v1/ping",
        handler: async (): Promise<Response> => {
          return Response.builder()
            .withStatusCode(StatusType.OK)
            .withBody({
              message: "success"
            }).build();
        }
      })
      .build();
  }

  async handle(event: APIGatewayProxyEvent): Promise<UnrestResponse> {
    return await this.unrest.execute(event);
  }
}

Controller Pattern

Create controllers to organize your API endpoints:

import { RequestProps, Response, StatusType, Unrest, MethodType } from '@pyriter/unrest';

export class UserController {
  constructor(private readonly unrest: Unrest) {
    this.unrest.withRoutes([
      {
        method: MethodType.GET,
        path: '/api/v1/users',
        handler: this.getAllUsers,
        thisReference: this,
      },
      {
        method: MethodType.GET,
        path: '/api/v1/users/{userId}',
        handler: this.getUserById,
        thisReference: this,
      },
      {
        method: MethodType.POST,
        path: '/api/v1/users',
        handler: this.createUser,
        thisReference: this,
      },
      {
        method: MethodType.PUT,
        path: '/api/v1/users/{userId}',
        handler: this.updateUser,
        thisReference: this,
      },
      {
        method: MethodType.DELETE,
        path: '/api/v1/users/{userId}',
        handler: this.deleteUser,
        thisReference: this,
      },
    ]);
  }

  async getAllUsers(request: RequestProps<undefined>): Promise<Response<User[] | string>> {
    try {
      const { apiGatewayEvent, queryStringParams } = request;
      const { limit, offset } = queryStringParams;
      
      // Your business logic here
      const users = await this.userService.getUsers({ limit, offset });
      
      return Response.builder<User[]>()
        .withStatusCode(StatusType.OK)
        .withBody(users)
        .build();
    } catch (error) {
      return Response.builder<string>()
        .withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
        .withBody(`Error fetching users: ${error}`)
        .build();
    }
  }

  async getUserById(request: RequestProps<undefined>): Promise<Response<User | string>> {
    try {
      const { urlParams } = request;
      const userId = urlParams.userId || '';
      
      if (!userId) {
        return Response.builder<string>()
          .withStatusCode(StatusType.BAD_REQUEST)
          .withBody('userId is required')
          .build();
      }

      const user = await this.userService.getUserById(userId);
      if (!user) {
        return Response.builder<string>()
          .withStatusCode(StatusType.NOT_FOUND)
          .withBody('User not found')
          .build();
      }

      return Response.builder<User>()
        .withStatusCode(StatusType.OK)
        .withBody(user)
        .build();
    } catch (error) {
      return Response.builder<string>()
        .withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
        .withBody(`Error fetching user: ${error}`)
        .build();
    }
  }

  async createUser(request: RequestProps<CreateUserRequest>): Promise<Response<User | string>> {
    try {
      const { body, apiGatewayEvent } = request;
      const { name, email, role } = body;
      
      // Validate required fields
      if (!name || !email) {
        return Response.builder<string>()
          .withStatusCode(StatusType.BAD_REQUEST)
          .withBody('Name and email are required')
          .build();
      }

      const user = await this.userService.createUser({ name, email, role });
      
      return Response.builder<User>()
        .withStatusCode(StatusType.CREATED)
        .withBody(user)
        .build();
    } catch (error) {
      return Response.builder<string>()
        .withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
        .withBody(`Error creating user: ${error}`)
        .build();
    }
  }

  async updateUser(request: RequestProps<UpdateUserRequest>): Promise<Response<User | string>> {
    try {
      const { urlParams, body, apiGatewayEvent } = request;
      const userId = urlParams.userId || '';
      const { name, email, role } = body;

      if (!userId) {
        return Response.builder<string>()
          .withStatusCode(StatusType.BAD_REQUEST)
          .withBody('userId is required')
          .build();
      }

      const updatedUser = await this.userService.updateUser(userId, { name, email, role });
      
      return Response.builder<User>()
        .withStatusCode(StatusType.OK)
        .withBody(updatedUser)
        .build();
    } catch (error) {
      return Response.builder<string>()
        .withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
        .withBody(`Error updating user: ${error}`)
        .build();
    }
  }

  async deleteUser(request: RequestProps<undefined>): Promise<Response<boolean | string>> {
    try {
      const { urlParams } = request;
      const userId = urlParams.userId || '';

      if (!userId) {
        return Response.builder<string>()
          .withStatusCode(StatusType.BAD_REQUEST)
          .withBody('userId is required')
          .build();
      }

      await this.userService.deleteUser(userId);
      
      return Response.builder<boolean>()
        .withStatusCode(StatusType.OK)
        .withBody(true)
        .build();
    } catch (error) {
      return Response.builder<string>()
        .withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
        .withBody(`Error deleting user: ${error}`)
        .build();
    }
  }
}

Service Handler Integration

Wire up your controllers in a main service handler:

import { APIGatewayProxyEvent } from 'aws-lambda';
import { Unrest, UnrestResponse } from '@pyriter/unrest';

export class ServiceHandler {
  constructor(
    private readonly userController: UserController,
    private readonly orderController: OrderController,
    private readonly productController: ProductController,
    private readonly unrest: Unrest,
  ) {
    // Controllers have configured their routes, build the Unrest instance
  }

  async handle(event: APIGatewayProxyEvent): Promise<UnrestResponse> {
    return await this.unrest.execute(event);
  }
}

Request Handling

URL Parameters

Access URL parameters using request.urlParams:

async getUserById(request: RequestProps<undefined>): Promise<Response<User | string>> {
  const { urlParams } = request;
  const userId = urlParams.userId || '';
  
  if (!userId) {
    return Response.builder<string>()
      .withStatusCode(StatusType.BAD_REQUEST)
      .withBody('userId is required')
      .build();
  }
  
  // Use userId in your business logic
}

Query String Parameters

Access query parameters using request.queryStringParams:

async getUsers(request: RequestProps<undefined>): Promise<Response<User[] | string>> {
  const { queryStringParams } = request;
  const { limit = '10', offset = '0', sortBy = 'name' } = queryStringParams;
  
  const users = await this.userService.getUsers({
    limit: parseInt(limit),
    offset: parseInt(offset),
    sortBy
  });
  
  return Response.builder<User[]>()
    .withStatusCode(StatusType.OK)
    .withBody(users)
    .build();
}

Request Body

Type your request body and access it via request.body:

interface CreateUserRequest {
  name: string;
  email: string;
  role?: string;
}

async createUser(request: RequestProps<CreateUserRequest>): Promise<Response<User | string>> {
  const { body } = request;
  const { name, email, role } = body;
  
  // Validate and process the request body
  if (!name || !email) {
    return Response.builder<string>()
      .withStatusCode(StatusType.BAD_REQUEST)
      .withBody('Name and email are required')
      .build();
  }
  
  const user = await this.userService.createUser({ name, email, role });
  
  return Response.builder<User>()
    .withStatusCode(StatusType.CREATED)
    .withBody(user)
    .build();
}

Response Building

Use the Response.builder() to construct standardized responses:

// Success response
return Response.builder<User>()
  .withStatusCode(StatusType.OK)
  .withBody(user)
  .build();

// Error response
return Response.builder<string>()
  .withStatusCode(StatusType.BAD_REQUEST)
  .withBody('Validation error: field is required')
  .build();

// Created response
return Response.builder<User>()
  .withStatusCode(StatusType.CREATED)
  .withBody(newUser)
  .build();

// Not found response
return Response.builder<string>()
  .withStatusCode(StatusType.NOT_FOUND)
  .withBody('Resource not found')
  .build();

Error Handling

Implement consistent error handling across your controllers:

async handleRequest<T>(requestFn: () => Promise<T>): Promise<Response<T | string>> {
  try {
    const result = await requestFn();
    return Response.builder<T>()
      .withStatusCode(StatusType.OK)
      .withBody(result)
      .build();
  } catch (error) {
    console.error('Request failed:', error);
    
    if (error.name === 'ValidationError') {
      return Response.builder<string>()
        .withStatusCode(StatusType.BAD_REQUEST)
        .withBody(`Validation error: ${error.message}`)
        .build();
    }
    
    if (error.name === 'NotFoundError') {
      return Response.builder<string>()
        .withStatusCode(StatusType.NOT_FOUND)
        .withBody(error.message)
        .build();
    }
    
    return Response.builder<string>()
      .withStatusCode(StatusType.INTERNAL_SERVER_ERROR)
      .withBody('Internal server error')
      .build();
  }
}

API Reference

Unrest

The routing library itself. It can execute an APIGatewayEvent and invoke the desired controller.

Unrest.builder()

Returns the builder for creating an instance of the unrest object.

MethodType

Enum for HTTP methods:

  • MethodType.GET
  • MethodType.POST
  • MethodType.PUT
  • MethodType.DELETE
  • MethodType.PATCH

StatusType

Enum for HTTP status codes:

  • StatusType.OK (200)
  • StatusType.CREATED (201)
  • StatusType.BAD_REQUEST (400)
  • StatusType.UNAUTHORIZED (401)
  • StatusType.FORBIDDEN (403)
  • StatusType.NOT_FOUND (404)
  • StatusType.INTERNAL_SERVER_ERROR (500)

RequestProps

Generic interface for request properties:

  • urlParams: URL path parameters
  • queryStringParams: Query string parameters
  • body: Request body (typed as T)
  • headers: Request headers
  • apiGatewayEvent: Original AWS API Gateway event
  • method: HTTP method
  • path: Request path

Response

Generic response interface:

  • statusCode: HTTP status code
  • body: Response body (typed as T)

Response.builder()

Builder pattern for constructing responses:

  • .withStatusCode(code): Set HTTP status code
  • .withBody(data): Set response body
  • .build(): Build the final response

Performance

Unrest uses a trie data structure for efficient route matching and includes local caching to improve lookup and response times. The library is designed to minimize latency in AWS Lambda environments.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.