@egodigital/express-controllers
    TypeScript icon, indicating that this package has built-in type declarations

    8.0.0 • Public • Published

    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: "hello@e-go-digital.com",
                    },
                    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.

    Install

    npm i @egodigital/express-controllers

    DownloadsWeekly Downloads

    87

    Version

    8.0.0

    License

    LGPL-3.0

    Unpacked Size

    142 kB

    Total Files

    16

    Last publish

    Collaborators

    • mkloubert
    • mkloubertego
    • ekegodigital