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

thresh

v1.1.0

Published

Decorative implementation of Express with TypeScript and dependency injection

Downloads

28

Readme

Thresh

npm version Coverage Status Build Status License: MIT

Decorative implementation of Express with TypeScript and dependency injection

Table of Contents

Installation

Install with npm

npm install thresh --save

Or yarn

yarn add thresh

Usage

Basic

The most basic implementation with no dependencies or middleware. This is an Express app listening on port 3000 for GET requests on '/hello'. As a note /hello could also be a RegExp, Express path expression or an array containing any combination of those.

import { Thresh, Route, Request, Response } from 'thresh';

@Thresh({ express: [3000] })
class ThreshApplication {
  @Route('/hello')
  helloWorld(req: Request, res: Response) {
    res.json({ hello: 'world' });
  }
}

new ThreshApplication();

Services

Services are created as singletons and are available and provided for in the decorator @Thresh. They are immediately available in the constructor of the class that provided them, as well as the constructor of all nested routers.

The ExpressService and RootService are available to the root application and every nested router. ExpressService notably contains the app variable which is the Express Application/Router. RootService is the ExpressService for the root application.

import {
  Thresh,
  Route,
  Request,
  Response,
  ExpressService,
  RootService
} from 'thresh';

class FooService {
  public foo: string = 'bar';
}

@Thresh({
  services: [FooService],
  express: [3000]
})
class ThreshApplication {
  constructor(
    private fs: FooService,
    private rs: RootService,
    private es: ExpressService
  ) {
    console.log(this.rs === this.es); // true
    this.es.app.use('/', myAwesomeMiddleware());
  }

  @Route('/hello') // GET http://localhost:3000/hello
  helloWorld(req: Request, res: Response) {
    res.json({ foo: this.fs.foo }); // { foo: 'bar' }
  }
}

new ThreshApplication();

Routers

Routers are essentially identical to Apps and are also declared with @Thresh. They will inherit services from their parents.

import { Thresh, Route, Request, Response } from 'thresh';

class FooService {
  public foo: string = 'bar';
}

@Thresh()
class ThreshRouter {
  constructor(fs: FooService) {}

  @Route('/hello') // GET http://localhost:3000/foo/hello
  helloWorld(req: Request, res: Response) {
    res.json({ foo: this.fs.foo }); // { foo: 'bar' }
  }
}

@Thresh({
  routers: [['/foo', ThreshRouter]]
  services: [FooService],
  express: [3000]
})
class ThreshApplication {}

new ThreshApplication();

Router Scope

Services provided in routers will receive their own singletons isolated from the router's parents.

import { Thresh, Route, Request, Response } from 'thresh';

class FooService {
  public foo: string = 'bar';
}

@Thresh({
  services: [FooService]
})
class ThreshRouter {
  constructor(fs: FooService) {
    console.log(this.fs.foo) // 'bar'
    this.fs.foo = 'banana';
  }

  @Route('/hello') // GET http://localhost:3000/foo/hello
  helloWorld(req: Request, res: Response) {
    res.json({ foo: this.fs.foo }); // { foo: 'banana' }
  }
}

@Thresh({
  routers: [['/foo', ThreshRouter]]
  services: [FooService],
  express: [3000]
})
class ThreshApplication {
  constructor(fs: FooService) {
    this.fs.foo = 'apple';
  }

  @Route('/hello') // GET http://localhost:3000/hello
  helloWorld(req: Request, res: Response) {
    res.json({ foo: this.fs.foo }); // { foo: 'apple' }
  }
}

new ThreshApplication();

Middleware

Middleware like routes, can also be chained by passing no parameters to the class method and returning an array of middleware.

import {
  Thresh,
  Route,
  Middleware,
  Request,
  Response,
  NextFunction
} from 'thresh';

@Thresh({ express: [3000] })
class ThreshApplication {
  @Middleware('/')
  addHero(req: Request, res: Response, next: NextFunction) {
    req.query.name = 'Peter Parker';
    next();
  }

  @Middleware('/villian')
  addVillian() {
    // ^-- No Parameters
    return [
      function(req: Request, res: Response, next: NextFunction) {
        console.log(`You're done ${req.query.name}!`);
        next();
      },
      function(req: Request, res: Response, next: NextFunction) {
        req.query.name = 'Doctor Octavius';
        next();
      }
    ];
  }

  @Route('/hello')
  hello(req: Request, res: Response) {
    res.send(`Hello, ${req.query.name}!`); // Hello, Peter Parker!
  }

  @Route('/villian')
  getVillian(req: Request, res: Response) {
    res.send(`Oh no, it's ${req.query.name}!`);
    // Oh no, it's Doctor Octavius
  }
}

new ThreshApplication();

Params

Parameter interceptors are assigned via the @Param decorator. Pass in a string with the parameter you want to intercept and act on.

import { Thresh, Route, Param, Request, Response, NextFunction } from 'thresh';

@Thresh({ express: [3000] })
class ThreshApplication {
  @Param('name')
  addName(req: Request, res: Response, next: NextFunction, name: any) {
    req.query.name = name + 'awesome';
    next();
  }

  @Route('/hello/:name')
  hello(req: Request, res: Response) {
    res.send(`Hello, ${req.query.name}!`); //Hello [name]awesome!
  }
}

new ThreshApplication();

HTTP Methods

All HTTP verbs that Express supports, Thresh does as well. They can either be typed out as a string or imported by the MethodTypes constant. If @Method is not defined, the route will default to GET.

import {
  Thresh,
  Route,
  Request,
  Response,
  NextFunction,
  Method,
  MethodTypes
} from 'thresh';

@Thresh({ express: [3000] })
class ThreshApplication {
  @Route('/hello')
  @Method(MethodTypes.Post) // POST: http://localhost:3000/hello
  helloWorld(req: Request, res: Response) {
    res.send(`Hello, world!`); // Hello, world!
  }

  @Route('/helloall')
  @Method('put') // PUT: http://localhost:3000/helloall
  helloAll(req: Request, res: Response) {
    res.send(`Hello, everyone!`); // Hello, everyone!
  }
}

new ThreshApplication();

Hooks

Several lifecycle hooks are available to modify the App/Router creation.

In Order:

  • onInit: Express and Services initialized, but no routes/routers/middleware added yet
  • afterInit: Just after the child Routers, routes and middleware have been added
  • onStart: Just before Express.listen is called, only called on the root application
  • afterStart: Just after Express.listen is called, only called on the root application
import {
  Thresh,
  Route,
  Request,
  Response,
  App,
  Injector,
  afterInit
} from 'thresh';

@Thresh({ express: [3000] })
class ThreshApplication implements afterInit {
  @Route('/hello')
  helloWorld(req: Request, res: Response) {
    res.send(`Hello, world!`);
  }

  afterInit(app: App, services: Injector) {
    console.log(app._router.stack); // List all routes
    app.use('/public', express.static('public')); // Serve public files
  }
}

new ThreshApplication();

Route/Middleware Ordering

If compiled to ES2015+ (ES6+) Routes and Middleware are applied exactly how they appear in the class from top to bottom. Compiling further back than that doesn't guarantee object property ordering ECMA-262. To overcome this a static $order: string[] can be provided with the exact order to apply routes/middleware in. Any @Routes or @Middleware not declared in $order will be applied after those in $order.

import { Thresh, Route, Request, Response, NextFunction } from 'thresh';

@Thresh({ express: [3000] })
class ThreshApplication implements afterInit {
  static $order = ['appliedFirst', 'appliedLast'];

  @Route('/hello')
  appliedLast(req: Request, res: Response) {
    res.send(`Hello, ${req.query.name}!`);
    // With $order: Hello, Peter Parker!
    // Without $order: Hello, undefined
    //    This is because appliedLast is applied first as
    //    it comes first in the class definition
  }

  @Middleware('/')
  appliedFirst(req: Request, res: Response, next: NextFunction) {
    req.query.name = 'Peter Parker';
    next();
  }
}

new ThreshApplication();

Still in Development

  • Providing Services within Services