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

piilani

v1.0.0-dev.26

Published

Just another Web application framework.

Readme

Piilani

Pee ‧ ee ‧ lawn ‧ ee - Hawaiian; to go up to the heavens/sky.

A framework for building cloud-based Node.js TypeScript back-end Web services.

⚠️ UNDER DEVELOPMENT: NOT PRODUCTION READY ⚠️

If you have any questions, development ideas, or you'd like to contribute, please contact me or create an issue on Github.

Getting Started

Installation

Add Piilani to your project.

NPM

npm i piilani

Yarn

yarn add piilani

Initialization

To initialize your project directory run the following Piilani command:

./node_modules/piilani/bin/cli init

This will install all required dependencies and create a config directory with config files for each supported environment.

Environment

There are 3 runtime environments and the PIILANI_ENV environment variable to specify which one the app is currently running in:

+-------------------+---------------+
| Environment       | PIILANI_ENV   |
+-------------------+---------------+
| development       | dev           |
+-------------------+---------------+
| non-production    | non-prod      |
+-------------------+---------------+
| production        | prod          |
+-------------------+---------------+

Configuration

After initializing the project (see Getting Started), you'll have 4 configuration files in your config directory. The config.json file is the default. The dev.config.json, non-prod.config.json, and prod.config.json files are for overriding the default configuration in the respective runtime environments (see Environment).

TypeScript Configuration

The following values must be set in your tsconfig.json.

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es6"
  },
}

Entry Point

At the entry point for the service, reflect-metadata must be imported. This is required for decorator support. In Piilani, the entry point is conventionally startup.ts. More on that in the Dependency Injection section.

import 'reflect-metadata';

Dependency Injection

Dependency injection is handled by tsyringe. The recommended way of working with the library is to create a startup.ts file at the root of your project's source code. In this file, we can include the required reflect-metadata package, and manage the Dependency Injection container.

The startup.ts MUST be included before any application code.

Example startup.ts
import 'reflect-metadata';
import { container } from 'tsyringe';

export const DependencyInjection = container;

container.register(/*token, value/class*/)

For more on dependency injection, see the tsyringe documentation. If your project grows very large, you can split the file and include the child files in the parent startup.ts.

TODO: This could be scripted. Options should exist for creating a startup.ts in a new or existing project.

Server

By default, the HTTP server will run unsecured. To run the server secured (over HTTPS), add the cert and key files to the config file(s).

The files are paths relative to the project's root directory.

{
  "listenPort": 443,
  "ssl": {
    "cert": "ssl/ssl.cert",
    "key": "ssl/ssl.key"
  }
}

Database

The app can be configured to use one or many databases. Currently, only PostgreSQL and Mongodb are officially supported. You can still use other databases, but certain convenience functions (like the getConnectionString method of the Config class) will be unusable.

Rest Controllers

The base of a RESTful Web service is the RestController.

import { injectable } from 'piilani/context';
import { RestController } from 'piilani/controllers/http';

@injectable()
class ListController extends RestController {
    // Class definition here...
}

This controller is now primed to handle incoming HTTP requests. Without specifying a basePath, the controller will handle routes with paths starting from the root /. Alternatively, the basePath can be set in the constructor.

import { inject, injectable } from 'piilani/context';

@injectable()
class ListController extends RestController {
    private gateway: IListsGateway;

    public constructor(@inject('IListsGateway')gateway: IListsGateway) {
        super()
        this.gateway = gateway;
        this.basePath = '/lists';
    }
}

Now all route paths handled by this controller will begin with /lists.

Handling HTTP Requests

To handle HTTP requests, use the get, post, put, and delete decorators.

The following method, annotated with the @get() decorator, will handle get requests to the controller's base path (/ by default, see the RestController section for details on how to set base path).

import { HttpResponse, NoContent, Ok, ServerError } from 'piilani/controllers/http/response';
import { del, get, post, put } from 'piilani/decorators/restful';

@injectable()
class ListController extends RestController {
    // code omitted.

    @get()
    public async getAllLists(): Promise<HttpResponse<List[]>> {
      const res = await this.gateway.findAsync();
      if (res.data) {
        return Ok(res.data);
      }
      if (res.class === 2) {
        return NoContent();
      }

      return ServerError();
    }
}

If you want the annotated methods to handle more specific routes, add the path (relative to the RestController's basePath) to the decorator's invocation.

@injectable()
export class ListsController extends RestController {
    // code omitted.

    @get('/index')
    public async getListIndex(): Promise<HttpResponse<ListIndex[]>> {
      const res = await this.gateway.findIndexAsync();

      if (res.data) {
        return Ok(res.data);
      }
      if (res.class === 2) {
        return NoContent();
      }

      return ServerError();
    }
}

Handling Path Parameters

TODO: Document the usage of @fromPath decorator.

Handling Query Parameters

import { fromQuery, get } from 'piilani/decorators/restful';

@injectable()
export class ListsController extends RestController {
    // code omitted.

    @get()
    public async getListById(@fromQuery('id') id: string): Promise<HttpResponse<List>> {
      const res = await this.gateway.findAsync({ id })

      if (res.data) {
        return Ok(res.data[0]);
      }
      if (res.class === 2) {
        return NoContent();
      }

      return BadRequest();
    }
}

Handling Request Body

import { fromBody, get } from 'piilani/decorators/restful';

@injectable()
export class ListsController extends RestController {
    // code omitted.

    @post()
    public async addList(@fromBody()list: Partial<Omit<List, 'id'>>): Promise<HttpResponse> {
      const res = await this.gateway.insertAsync(new List(list));

      if (res.class === 1) {
        return Created();
      }
      if (res.class === 2) {
        return BadRequest();
      }

      return ServerError();
    }
}

Authentication

TODO: Document the Authentication configuration.

// startup.ts
import { Authentication } from 'piilani/services/auth/Authentication';

DependencyInjection.register(Gateways.Users, UsersMongoGateway);

Authentication.configure({
  // Minutes until keys expire.
  exp: 60,
  gateway: DependencyInjection.resolve<IUsersGateway>(Gateways.Users),
  privateKey: '<private-key or secret>',
});

Authenticated Endpoints

To require users to be authenticated to access routes, add the @authenticated decorated to the Route's method.

This will require uses to pass a JWT Bearer token along with the request in the HTTP Authorization header.

import { authenticated, post, fromBody } from 'piilani/decorators/restful';

class ListsController extends RestController {
  // --- code omitted -- //

  @authenticated()
  @post()
  public async addList(@fromBody()list: List): Promise<HttpResponse> {
    const res = await this.gateway.insert(new List(list));

    if (res.class === 1) {
      return Created();
    }
    if (res.class === 2) {
      return BadRequest();
    }

    return ServerError();
  }
}