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

@egodigital/express-controllers

v8.0.0

Published

Sets up controllers for Express framework.

Downloads

72

Readme

npm

express-controllers

Sets up controllers, which are running with Express.js.

Install

Execute the following command from your project folder, where your package.json file is stored:

npm install --save @egodigital/express-controllers

Samples

Example code can be found in express-controllers-samples repository.

Usage

Build

# install modules
npm install

# build
npm run build

Requirements

TypeScript

You have to enable decorator feature in your tsconfig.json file:

{
    "compilerOptions": {
        // ...

        "experimentalDecorators": true
    },
    
    // ...
}

Examples

Quick start

First create a root directory, where all your controllers will be stored and implemented, lets say /controllers.

Then create a /controllers/index.ts (if using TypeScript) and implement an exported / public Controller class with the following skeleton:

import { Request, Response } from 'express';
import { ControllerBase, GET, POST, schema } from '@egodigital/express-controllers';

interface INewUser {
    email?: string;
    password: string;
    username: string;
}

const NEW_USER_SCHEMA = schema.object({
    email: schema.string()
        .optional(),
    password: schema.string()
        .min(1)
        .required(),
    username: schema.string()
        .required(),
});

/**
 * /controllers/index.ts
 *
 * Base path: '/'
 */
export class Controller extends ControllerBase {
    /**
     * [GET] / endpoint
     *
     * 'index' will mapped to realtive '/' path by default.
     */
    @GET()
    public async index(req: Request, res: Response) {
        return res.status(200)
            .send('Hello, e.GO!');
    }

    /**
     * [GET] /foo endpoint
     *
     * Other method names than 'index', will always be mapped
     * to realtive '/{METHOD_NAME}' path
     */
    @GET()
    public async foo(req: Request, res: Response) {
        return res.status(200)
            .send('Hello, foo!');
    }

    /**
     * [POST] /foo/:foo_id endpoint
     *
     * Define relative path explicitly.
     */
    @POST('/foo/:foo_id')
    public async foo_with_post(req: Request, res: Response) {
        return res.status(200)
            .send('Hello, foo.POST: ' + req.params['foo_id']);
    }

    /**
     * [POST] /users endpoint
     *
     * Check JSON input via joi schema.
     */
    @POST({
        path: '/users',
        schema: NEW_USER_SCHEMA,
    })
    public async create_new_user(req: Request, res: Response) {
        const NEW_USER: INewUser = req.body;

        // TODO ...

        return res.status(200)
            .send('Created new user: ' + NEW_USER.username);
    }
}

For loading and initializing the controllers from /controllers, simply create a /index.ts file and use the following code snippet:

import * as express from 'express';
import { initControllers } from '@egodigital/express-controllers';

const app = express();

initControllers({
    app,
    cwd: __dirname + '/controllers',
    files: ['**/*.ts', '**/*.js']
});

app.listen(8080, () => {
    // server now running
});

The library will scan the complete /controllers folder structure and map the endpoints by that structure.

You can also use other filenames as index.ts. For example, if you would like to implement a /foo/bar endpoint, create a /controllers/foo/bar.ts and use the following snippet:

import { Request, Response } from 'express';
import { ControllerBase, GET } from '@egodigital/express-controllers';

/**
 * /controllers/foo/bar.ts
 *
 * Base path: '/foo/bar'
 */
export class Controller extends ControllerBase {
    /**
     * [GET] /foo/bar endpoint
     */
    @GET()
    public async index(req: Request, res: Response) {
        // TODO
    }

    /**
     * [GET] /foo/bar/xyz endpoint
     */
    @GET()
    public async xyz(req: Request, res: Response) {
        // TODO
    }

    /**
     * [GET] /foo/bar/tm endpoint
     */
    @GET('/tm')
    public async xyz(req: Request, res: Response) {
        // TODO
    }
}

Serialize

import { Request, Response } from 'express';
import { ControllerBase, GET, ResponseSerializerContext } from '@egodigital/express-controllers';

/**
 * /controllers/index.ts
 *
 * Base path: '/'
 */
export class Controller extends ControllerBase {
    // serialize the results of any
    // controller route method and
    // send each as response
    public async __serialize(context: ResponseSerializerContext) {
        return context.response
            .header('Content-Type', 'application/json')
            .send(JSON.stringify(
                context.result  // result of 'index()', e.g.
            ));
    }

    /**
     * [GET] / relative endpoint
     */
    @GET()
    public async index(req: Request, res: Response) {
        // this object is serialized and
        // send by '__serialize()' (s. above)
        return {
            success: true,
            data: {
                'TM': '1979-09-05 23:09'
            },
        };
    }
}

Middlewares

import * as express from 'express';
import { ControllerBase, POST } from '@egodigital/express-controllers';

interface INewUser {
    email?: string;
    password: string;
    username: string;
}

/**
 * /controllers/index.ts
 *
 * Base path: '/'
 */
export class Controller extends ControllerBase {
    // define one or more middlewares
    // for each route endpoint
    public __use = [
        express.urlencoded({ extended: true }),
    ];

    /**
     * [POST] /users endpoint
     */
    @POST('/users')
    public async new_user(req: express.Request, res: express.Response) {
        const NEW_USER: INewUser = req.body;

        // TODO ...

        return res.status(200)
            .send('Created new user: ' + JSON.stringify(
                NEW_USER, null, 2
            ));
    }
}

Error handling

import * as express from 'express';
import { ControllerBase, GET, RequestErrorHandlerContext } from '@egodigital/express-controllers';

/**
 * /controllers/index.ts
 *
 * Base path: '/'
 */
export class Controller extends ControllerBase {
    // handle exceptions
    public async __error(context: RequestErrorHandlerContext) {
        return context.response
            .status(500)
            .send('SERVER ERROR: ' + context.error);
    }

    /**
     * [GET] / endpoint
     */
    @GET()
    public async index(req: express.Request, res: express.Response) {
        // all request error, like that
        // will be handled by
        // '__error()' method
        throw new Error('Test error!');
    }
}

Authorize

import * as express from 'express';
import { Authorize, AuthorizeFailedHandlerContext, AuthorizeHandlerContext, ControllerBase, GET, RequestErrorHandlerContext } from '@egodigital/express-controllers';

/**
 * /controllers/index.ts
 *
 * Base path: '/'
 */
export class Controller extends ControllerBase {
    // check if authorized
    public async __authorize(context: AuthorizeHandlerContext) {
        // return (true) or (false)
        // or a non empty string, which is returned as error message by default

        // default: (false)

        return 'The authorization has been failed';
    }

    // handle failed authorization
    public async __authorizeFailed(context: AuthorizeFailedHandlerContext) {
        // s. result of __authorize()
        const ERROR_MSG = context.result as string;

        return context.response
            .status(401)
            .send('AUTHORIZE FAILED: ' + ERROR_MSG);
    }


    /**
     * [GET] / endpoint
     */
    @Authorize()
    @GET()
    public async index(req: express.Request, res: express.Response) {
        // this will only be invoked
        // if __authorize() returns (true)
        // or nothing (null, undefined or empty string)

        return res.status(204)
            .send();
    }
}

Swagger

First, define the main information of the document:

import * as express from 'express';
import { initControllers } from '@egodigital/express-controllers';

const app = express();

initControllers({
    app,
    cwd: __dirname + '/controllers',
    swagger: {
        // s. https://swagger.io/docs/specification/2-0/describing-responses/
        definitions: {
            'SuccessResponse': {
                "type": "object",
                "properties": {
                    "success": {
                        "description": "Indicates if operation was successful or not.",
                        "type": "boolean"
                    },
                    "data": {
                        "description": "The result data.",
                        "type": "string"
                    },
                }
            }
        },
        document: {
            host: 'api.example.com',
            info: {
                contact: {
                    email: "[email protected]",
                },
                description: "Describes all API endpoints.",
                title: "Test API",
                version: "1.0.0",
            },
            schemes: ['http', 'https'],
            tags: {
                'test': 'A test tag',
            },
        },
        title: 'Swagger Test',
    },
});

app.listen(8080, () => {
    console.log('Swagger document: http://localhost:8080/swagger');
});

Now use @Swagger decorator for each of your method, to document your API (for more information, visit Paths and Operations ):

import { Request, Response } from 'express';
import { ControllerBase, GET, Swagger, SwaggerPathDefinitionUpdaterContext } from '@egodigital/express-controllers';


// update each path definition with default values (s. below)
function pathDefinitionUpdater(ctx: SwaggerPathDefinitionUpdaterContext) {
    // Bad Request
    ctx.definition['responses']['400'] = {
        "description": "Bad request!"
    };

    // Internal Server Error
    ctx.definition['responses']['500'] = {
        "description": "Operation has failed!"
    };
}


/**
 * /controllers/api/index.ts
 *
 * Base path: '/api'
 */
export class Controller extends ControllerBase {
    /**
     * [GET]  /api
     */
    @GET()
    // s. https://swagger.io/docs/specification/2-0/paths-and-operations/
    @Swagger({
        "tags": [
            "test"
        ],
        "summary": "A test.",
        "produces": [
            "application/json"
        ],
        "parameters": [
            {
                "in": "header",
                "name": "X-My-Header",
                "required": false,
                "type": "string"
            },
        ],
        "responses": {
            "200": {
                "description": "Operation was successful.",
                "schema": {
                    "$ref": "#/definitions/SuccessResponse",
                }
            },
        }
    }, pathDefinitionUpdater)
    public async index(req: Request, res: Response) {
        return res.json({
            success: true,
            data: 'Swagger test: OK',
        });
    }
}

Documentation

The API documentation can be found here.