# @egodigital/express-controllers

> Sets up controllers for Express framework.

Latest version **8.0.0** (published 2021-02-07) · LGPL-3.0 license · 0 weekly downloads

## Install

```sh
npm install @egodigital/express-controllers
pnpm add @egodigital/express-controllers
yarn add @egodigital/express-controllers
bun add @egodigital/express-controllers
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 8.0.0 |
| Published | 2021-02-07 |
| First published | 2019-05-02 |
| Weekly downloads | 0 |
| License | LGPL-3.0 |
| TypeScript types | none |
| Module format | CommonJS |
| Node | >=12.0.0 |
| Dependencies | 14 |
| Unpacked size | 138.2 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 7 |
| Author | e.GO Digital GmbH, Aachen, Germany |
| Maintainers | mkloubert, mkloubertego, ekegodigital |
| Keywords | express, api, controller, mvc, http, server, rest, restful, router, app, typescript, authorization, decorator, annotation, metadata, reflection, attribute |

## Links

- npm: https://www.npmjs.com/package/@egodigital/express-controllers
- Repository: https://github.com/egodigital/express-controllers
- Homepage: https://github.com/egodigital/express-controllers#readme
- Issues: https://github.com/egodigital/express-controllers/issues
- npm.io page: https://npm.io/package/@egodigital/express-controllers

## Dependencies (14)

- [joi](https://npm.io/package/joi.md) ^17.3.0
- [lodash](https://npm.io/package/lodash.md) ^4.17.20
- [moment](https://npm.io/package/moment.md) ^2.29.1
- [express](https://npm.io/package/express.md) ^4.17.1
- [js-yaml](https://npm.io/package/js-yaml.md) ^4.0.0
- [fast-glob](https://npm.io/package/fast-glob.md) ^3.2.5
- [is-stream](https://npm.io/package/is-stream.md) ^2.0.0
- [@types/joi](https://npm.io/package/@types/joi.md) ^17.2.3
- [@types/node](https://npm.io/package/@types/node.md) ^12.19.16
- [@types/lodash](https://npm.io/package/@types/lodash.md) ^4.14.168
- [@types/express](https://npm.io/package/@types/express.md) 4.17.1
- [@types/js-yaml](https://npm.io/package/@types/js-yaml.md) ^4.0.0
- [swagger-ui-express](https://npm.io/package/swagger-ui-express.md) ^4.1.6
- [@types/swagger-ui-express](https://npm.io/package/@types/swagger-ui-express.md) ^4.1.2

## Alternatives

- [express-promise-router](https://npm.io/package/express-promise-router.md) — 736.1K weekly downloads
- [next-usequerystate](https://npm.io/package/next-usequerystate.md) — 29.8K weekly downloads
- [@bitkyc08/opencodex](https://npm.io/package/@bitkyc08/opencodex.md) — 4.6K weekly downloads
- [lynkr](https://npm.io/package/lynkr.md) — 575 weekly downloads
- [baremetal.js](https://npm.io/package/baremetal.js.md) — 42 weekly downloads

## Recent versions

- 8.0.0 (latest) — 2021-02-07
- 7.2.0 — 2021-02-07
- 7.1.0 — 2021-02-06
- 7.0.1 — 2021-01-29
- 7.0.0 — 2021-01-29
- 6.2.0 — 2020-07-20
- 6.1.4 — 2020-04-29
- 6.1.3 — 2020-04-15
- 6.1.2 — 2020-04-14
- 6.1.1 — 2020-02-05
- 6.1.0 — 2020-01-31
- 6.0.0 — 2020-01-29
- 5.4.0 — 2020-01-22
- 5.3.0 — 2019-10-14
- 5.2.0 — 2019-09-10
- … 45 more at https://npm.io/package/@egodigital/express-controllers/versions

## README

[![npm](https://img.shields.io/npm/v/@egodigital/express-controllers.svg)](https://www.npmjs.com/package/@egodigital/express-controllers)

# express-controllers

Sets up controllers, which are running with [Express.js](https://expressjs.com/).

## Install

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

```bash
npm install --save @egodigital/express-controllers
```

## Samples

Example code can be found in [express-controllers-samples](https://github.com/egodigital/express-controllers-samples) repository.

## Usage

### Build

```bash
# install modules
npm install

# build
npm run build
```

### Requirements

#### TypeScript

You have to enable [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) feature in your `tsconfig.json` file:

```jsonc
{
    "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](https://www.typescriptlang.org/)) and implement an exported / public `Controller` class with the following skeleton:

```typescript
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:

```typescript
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:

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
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:

```typescript
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
](https://swagger.io/docs/specification/2-0/paths-and-operations/)):

```typescript
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](https://egodigital.github.io/express-controllers/).

---
_Source: https://npm.io/package/@egodigital/express-controllers · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
