Skip to content

Controllers details

andreabbondanza edited this page May 29, 2023 · 13 revisions

Controllers definition details

Create a new controller to expose a new entity API it's quietly simple.

Naming a controller

You need to add the controller into the controllers folder and the file must follow this naming rule: mycontroller.controller.ts (details here 👍)

Your controller with all the endpoints will be loaded and initialized automatically.

The controller must extends the Controller class to all the services (dbservices, logservices, whatever service you want) and it's also needed to execute the automatic build initialization.

So you must do something like this:

export class AuthController extends Controller {
  public constructor(env: AppEnvironment) {
    super(env, "auth");
  }
}

I suggest you to add the Controller word to your classname, for example OrderController. This way you can avoid collision with other object (for example an Order object)

NOTE: when you pass the path to the super, it must end without "/" :exclamation

Where you get via dependency injection the app environment and you pass it to the super with the controller level routing path.

Route object and endpoints

Now, the controller will be the entry point for your API. This happen via the classes' methods definition. The idea is that every method is an endpoint and return a Route object.

NOTE: It's important that the endpoint returns the Route object. If not, it will cause EXCEPTIONS during the Server Initialization.

Route Object:

export type Method = "post" | "put" | "patch" | "get" | "delete";

export class Route {
  /**
   * Routing path
   */
  public readonly path: string;
  /**
   * Endpoint roles
   */
  public readonly roles: Roles[];
  /**
   * Routing regex test
   */
  public readonly test: RegExp;
  /**
   * Routing method
   */
  public readonly method: Method;
  /**
   * Create routing
   * @param path Path
   * @param roles roles
   * @param test regex
   * @param method method
   * @returns routing object
   */
  public constructor(
    path: string,
    roles: Roles[],
    test: RegExp,
    method: Method = "get"
  ) {
    this.path = path;
    this.roles = roles;
    this.test = test;
    this.method = method;
  }
}

No endpoint methods

If you want add a method that is not an endpoint you can do it put the _ character before the method name, for example a built in common method is _registerEndpoint (we will see soon what this method do).

   /**
     * Regiter a new endpoint
     * @param path path of the endpoint
     * @param roles roles for the endpoint
     * @param method endopint method
     * @returns endpoint object
     */
    public _registerEndpoint(path: string, roles: Roles[], method: Method = "get"): Endpoint
    {
        return new Endpoint(this.server, this._buildRoute(path, roles, method));
    }

or a your method:

export class AuthController extends Controller {
  public constructor(env: AppEnvironment) {
    super(env, "public/auth");
  }
  //this method will not be routed (if you check, you don't need to return Route object)
  public _notRoutedMethod(name: string) {
    this.log.info(`Hello ${name}`);
  }
}

Create and register an endpoint

Like said before, create an endpoint it's very easy, lets see:

/**
     * We are inside a Controller
     * @returns
     */
    public getEmail(): Route
    {
      // we register the endpoint and return the Route Object
        return this._registerEndpoint(
            "/checkmail/:email",
            ["public"]).endpoint(
                async (req, res) =>
                {
                    const response: IStandardResponse<any> = initSR();
                    try
                    {
                        const mail = req.params.email;
                        const aservice = this.initService<AuthService>(new AuthService());
                        const user = await aservice.getAuthByEmail(mail);
                        if (user)
                        {
                            response.Message = "Email già presente";
                            response.Data = true;
                            res.send(response);
                        }
                        else
                        {
                            response.Data = false;
                            res.status(200).send(response);
                        }
                    } catch (err)
                    {
                        this.log.fileLog(JSON.stringify(err));
                        response.Error.Desc = (err as any).message;
                        res.status(500).send(response);
                    }
                }).route;
    }

You can see in the code that to register our endpoint we use the function _registerEndpoint. This function accepts 3 arguments:

  • The path;
  • The roles (we will speak about this when we will talk about auth via the middleware);
  • The method (default is get ).

This function return an Endpoint object. This one contains the endpoint method that accept our express callback. To be clear, the one we will pass to app.get(callback) in normal express use.

Note that at the end of the function endpoint we return the Route via dot notation .route

Endpoint handler and Custom Data

You can also add an handler to the endpoint, for example:

/**
     * We are inside a Controller
     * @returns
     */
    public getEmail(): Route
    {
      // we register the endpoint and return the Route Object
        return this._registerEndpoint(
            "/checkmail/:email",
            ["public"])  
            .handler((e: AppEnvironment) =>
            {
                return (req, res, next) =>
                {
                    console.log("This is an example of an handler");
                    next();
                }
            }).
            endpoint(
                async (req, res) =>
                {
                    const response: IStandardResponse<any> = initSR();
                    try
                    {
                        const mail = req.params.email;
                        const aservice = this.initService<AuthService>(new AuthService());
                        const user = await aservice.getAuthByEmail(mail);
                        if (user)
                        {
                            response.Message = "Email già presente";
                            response.Data = true;
                            res.send(response);
                        }
                        else
                        {
                            response.Data = false;
                            res.status(200).send(response);
                        }
                    } catch (err)
                    {
                        this.log.fileLog(JSON.stringify(err));
                        response.Error.Desc = (err as any).message;
                        res.status(500).send(response);
                    }
                }).route;
    }

This is very useful if you want to add some middleware to your endpoint. An example is the multipart-formdata middleware that we will see in the next section. In this example we will use the connect-multiparty package

/**
     * We are inside a Controller
     * @returns
     */
    public addFile(): Route
    {
       return this._registerEndpoint("/id/:id/allegato", ["public"], "post", {isApi: true}).handler((req, res, next) =>
        {
            console.log("I'm the handler")
            next();
        }).handler(multipart()).endpoint(
            async (req, res) =>
            {
              ...
            }).route;
    }

In this method you can see that we use the handler method twice. This is because we want to add two middlewares to our endpoint. You can also see the use of the custom data. This is an object that you can pass to the _registerEndpoint method. This object will be passed to the Route object and you can access it in the Route object via the customData property.

Help methods from controller

If you give a look to this row in the code above:

const pservice = this.initService<AuthService>(new AuthService());

This method consent you to initialize a service with all environment and basic subservices (like database or filesystem) in it. You will understand better this in the Services Wiki page.

Same for this code:

const response: IStandardResponse<any> = initSR();

This code consent you to init the StandardResponse object that we'll use like response in all our endponts. It's a shortcut instead to initialize every time the object.

if (!body.email || !REGEX_EMAIL.test(body.email)) return res.status(400).send(initSR({ Message: "Invalid email", Error: { Num: 400, Desc: "Bad Email" } }));

if (!body.pwd) return res.status(400).send(initSR({ Message: "Invalid password", Error: { Num: 400, Desc: "Bad Password" } }));
                       ```

You'll undestand better the 'StandardObject' in the dedicated page.

Clone this wiki locally