Business logic (regardless of the type of resource, such as an API, a cron job, etc.) should perform a specific action and not worry about everything else like errors, formatting or logging.

Wrappers are functions that “wrap” the original function and are responsible for handling all the repetitive actions to avoid having to write them each time. This way, the business logic remains clean and focused, consistent with the DRY methodology.

The main function in the handler is the function that will be invoked by API Gateway (in my case, I work with AWS), but the business logic is actually contained in the main function, but executed by the wrapper.

export const main = Util.handler(async (event) => {
  // this is my business logic, clean and focused.
  await dynamoDb.send(new PutCommand(params))
  return JSON.stringify(params.Item)
}) // everything else is handled by the Util.handler wrapper

These wrappers can be placed before (checking permissions, preparing the environment) or after (catching errors, formatting the result, logging).

export namespace Util {
  export function handler(
    lambda: (evt: APIGatewayProxyEvent, context: Context) => Promise<string>,
  ) {
    return async function (event: APIGatewayProxyEvent, context: Context) {
      // 1. logging
      console.log(`[START] ${context.functionName}`)
 
      // 2. pre-auth check
      const token = event.headers?.["authorization"]
      if (!token || !token.startsWith("Bearer valid_token")) {
        throw new AuthError()
      }
 
      let body: string, statusCode: number
 
      try {
        // run the Lambda (pure business logic)
        body = await lambda(event, context)
        statusCode = 200
      } catch (error) {
        // 3. custom error
        if (error instanceof AuthError) {
          statusCode = 401
          body = JSON.stringify({ error: error.message })
        } else {
          statusCode = 500
          body = JSON.stringify({ error: "Internal error." })
        }
      } finally {
        // 4. logging
        console.log(`[END] End. Status: ${statusCode}`)
 
        // 5. HTTP response
        return { statusCode, body }
      }
    }
  }
}
  • The business logic takes care of what to do
  • The wrapper takes care of how to manage execution

This is a use case for higher-order functions.