express icon indicating copy to clipboard operation
express copied to clipboard

Using express as a plugin?

Open danimydev opened this issue 1 year ago • 1 comments

I am finding very useful to make use of adapter functions so you can write plain TypeScript (JavaScript) controllers and use them in express without compromising them.

A simple example:

Define your own request and response objects in which you will add or retrieve data.

// ./web/types.ts
export type HttpRequest = {
    headers: any,
    params: any,
    body: any,
}

export type HttpResponse = {
    statusCode: number,
    body: any,
}

export interface HttpController {
    execute: Function
}

export enum HttpStatusCodes {
    'OK' = 200,
    'RESOURCE_CREATED' = 201,
    'FORBIDDEN' = 401,
    'INTERNAL_ERROR' = 500,
}

Define your express adapter that receives your controller and returns a compatible express handler, this functions calls the execute method on the controller and rely on it for all the logic that will come next, it will expect a status Code and a body which are defined in types.ts


// ./web/app.ts
import express, { Request, Response } from 'express';
import { HttpRequest, HttpResponse, HttpController } from './types';

function adaptRequest(controller: HttpController) {
  return async (req: Request, res: Response) => {
    try {
      const httpRequest: HttpRequest = {
        headers: req.headers,
        params: req.params,
        body: req.body,
      }
      const httpResponse: HttpResponse = await controller.execute(httpRequest);
      res.status(httpResponse.statusCode).json(httpResponse.body);
    } catch (error: any) {
      res.status(500).json({
        error: error.message
      });
    }
  }
}

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.post('/jwt/sign', adaptRequest(signController));

An this is the controller you define implementing the interface, which you also define :). I believe the controller should also handle only plain request and response as JavaScript objects and probably receive a use case via constructor that will execute logic. (I didn't do it here but anyways).

import jwt from 'jsonwebtoken';
import { HttpRequest, HttpResponse, HttpController, HttpStatusCodes } from '../web/types';

export default class SignController implements HttpController {

    private secretKey: string;

    constructor(secretKey: string) {
        this.secretKey = secretKey;
    }

    execute(httpRequest: HttpRequest): HttpResponse {
        try {
            const { body } = httpRequest;
            const token = jwt.sign(body, this.secretKey);
            return {
                statusCode: HttpStatusCodes.OK,
                body: {
                    token: token,
                }
            }
        } catch (error: any) {
            return {
                statusCode: HttpStatusCodes.INTERNAL_ERROR,
                body: {
                    error: error.message,
                }
            }
        }
    }
}

What do you think about this approach? I think about two cases such as redirect and middleware that at this point my solution is to create an expressMiddlewareAdapter function and a redirectAdapterFunction and use them depending on the case I got.

danimydev avatar Sep 17 '22 05:09 danimydev