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

el-abstracto

v1.0.9

Published

A module to help abstracting different server implementations (e.g. Express, Hapi, ...) from the actual usage to break dependencies and reduce refactoring hassles.

Downloads

149

Readme

DEPRECATED

⚠️ DO NOT USE THIS REPOSITORY AND THE CORRESPONDING NPM PACKAGE ANYMORE! ⚠️

The idea behind AbstractServer is to abstract the logic of a Node.js server from the actual implementation which typically depends on a specific framework e.g. Express. This way it's easier to replace the framework if required since the logic doesn't have to be implemented again. To realize this idea, the logical parts of the server are usually implemented by a class derived from AbstractServer. However, this class (let's call it AppServer) does not implement the server framework's specific functions but rather keeps them abstract as well. Finally, a third class (let's call it ExpressServer) derives from AppServer and implements the functions which are framework specific. This might seem tedious at the beginning but saves a lot of time if the framework needs to be changed or gets outdated. Please feel free to participate on this project as I'm pretty sure there's still a lot to improve.


Installation

npm install el-abstracto

Example

AppServer

/* Abstract implementation of the routes and the handling. This class does not depend on a specific framework. */
export abstract class AppServer extends AbstractServer {
    /* Define all routes required by the App and the handlers which do the logical processing. */
    protected _defineRoutes(): IRoute[] {
        return [{
            method: RequestMethod.GET,
            route: '/hello',
            children: [{
                route: '/world', /* Listen to /hello/world. */

                /* Use several request handlers. The second one is only executed if the first one succeeds. */
                handler: [this._helloMiddleware, this._getWorldHandler],
            }]
        }, {
            method: RequestMethod.GET,
            route: '/:value', /* Listen to /:value. */
            handler: [this._getValueHandler],
        }] as IRoute[];
    }

    protected abstract _getMethod(...args: unknown[]): RequestMethod | null;
    protected abstract _getQuery(...args: unknown[]): Query;
    protected abstract _getPath(...args: unknown[]): string;
    protected abstract _getParams(...args: unknown[]): Params;
    protected abstract _getBody(...args: unknown[]): Body;
    protected abstract _getHeaders(...args: unknown[]): Headers;
    protected abstract _setHeader(header: string, value: HeaderValue, ...args: unknown[]): boolean;
    protected abstract _setStatus(status: number, ...args: unknown[]): boolean;
    protected abstract _send(body?: Body, ...args: unknown[]): Promise<void>;
    protected abstract _connect(config?: IServerConfig | undefined): Promise<void>;
    protected abstract _disconnect(): Promise<void>;
    protected abstract _transformPath(path: string): string;
    protected abstract _addRoute(method: RequestMethod, route: string, handler: RequestHandlerInternal): Promise<boolean>;

    private async _helloMiddleware(_: RequestHandlerRequest, response: RequestHandlerResponse, next: RequestNextHandler): Promise<void> {
        response.body = ['Hello']; /* Set body as array. */
        response.status = 200;

        return next();
    }

    private _getWorldHandler(_: RequestHandlerRequest, response: RequestHandlerResponse): Promise<void> {
        /* Append to previously defined body array. */
        (response.body as unknown[]).push('World');
        response.status = 200;

        return response.send() as Promise<void>;
    }

    private _getValueHandler(request: RequestHandlerRequest, response: RequestHandlerResponse): Promise<void> {
        response.body = `Provided param: ${request.params.value}`;
        response.status = 200;

        return response.send() as Promise<void>;
    }
}

ExpressServer

/* Actual implementation which depends on the Express framework. */
export class ExpressServer extends AppServer {
    private _app = express();
    private _server: any;

    protected _getMethod(req: Request): RequestMethod | null {
        let method = null;

        /* Map Express request method to AbstractServer request method. */
        switch (req.method.toUpperCase()) {
            case 'GET': method = RequestMethod.GET; break;
            case 'POST': method = RequestMethod.POST; break;
            case 'PATCH':
            case 'UPDATE': method = RequestMethod.PATCH; break;
            case 'DELETE': method = RequestMethod.DELETE; break;
        }
        return method;
    }

    protected _getQuery(req: Request): Query {
        /* Get Express request query. */
        return req.query;
    }

    protected _getPath(req: Request): string {
        /* Get Express request path. */
        return req.path;
    }

    protected _getParams(req: Request): Params {
        /* Get Express request params. */
        return req.params;
    }

    protected _getBody(req: Request): Body {
        /* Get Express request body. */
        return req.body;
    }

    protected _getHeaders(req: Request): Headers {
        /* Get Express request headers. */
        const headers: Headers = {};

        Object.entries(req.headers).filter(([_, value]) => value).forEach(([key, value]) => headers[key] = value || '');
        return headers;
    }

    protected _setHeader(header: string, value: HeaderValue, _: Request, res: Response): boolean {
        res.setHeader(header, value);
        return true;
    }

    protected _setStatus(status: number, _: Request, res: Response): boolean {
        res.status(status);
        return true;
    }

    protected _send(body: Body, _: Request, res: Response): Promise<void> {
        /* Send body. */
        if (!body) {
            res.send();
        } else if ((body instanceof Object) || (body as any instanceof Array)) {
            res.json(body);
        } else {
            res.send(body);
        }
        return Promise.resolve();
    }

    protected _connect(config?: IServerConfig | undefined): Promise<void> {
        return new Promise((resolve, reject) => {
            const port = config?.port || 80;

            /* Start listening to specified port. */
            this._server = this._app
                .listen(port, () => {
                    console.log(`Server listening to port ${port}`);
                    resolve();
                 })
                .on('error', (err) => reject(err));
        });
    }

    protected _disconnect(): Promise<void> {
        return new Promise((resolve) => this._server ?
            this._server.close(() => resolve()) : Promise.reject());
    }

    protected _transformPath(path: string): string {
        /* Since AbstractServer uses the same pattern as Express (pathToRegExp), no transformation is necessary. */
        return path;
    }

    protected _addRoute(method: RequestMethod, route: string, handler: RequestHandlerInternal): Promise<boolean> {
        let promise;

        /* Use the appropriate handler to serve a request for the provided route. */
        switch (method) {
            case RequestMethod.GET: this._app.get(route, handler); break;
            case RequestMethod.POST: this._app.post(route, handler); break;
            case RequestMethod.PATCH: this._app.patch(route, handler); break;
            case RequestMethod.DELETE: this._app.delete(route, handler); break;

            default: promise = Promise.reject('Unknown method');
        }
        return promise || Promise.resolve(true);
    }
}

Additional info

If you use InversifyJS or some other Inversion of Control framework, ensure to make AbstractServer injectable. In the case of InversifyJS this would work as follows.

/* Make sure, AbstractServer is injectable. */
decorate(injectable(), AbstractServer);

TODO

  • [ ] Implement streaming capabilites (RequestHandlerResponse should act as output stream)
  • [ ] Implement TLS and certificate handling