Skip to content

collierrgbsitisfise/serverless-bonk-template

Repository files navigation

⚑️ Serverless-Bonk-Boilerplate (aws cloud provider) ⚑️

serverless License: MIT Contributors PRs Welcome node master status AWS JEST NODEJS TypeScript


Serverless boilerplate based on serverless-webpack + typescript. Define ready to deploy project with predefined Β scripts, linter-prettier rules, basic lib and helpers. Based on pseudo Onion Architecture: lambda[controller] -> (services + helpers)[[domain layer]] -> repositories.

In order to dig deeper in onion architecture check this boilerplate: https://github.com/Melzar/onion-architecture-boilerplate


content

Usage πŸ‘¨β€πŸ’»/πŸ‘©β€πŸ’»

$: npm i -g serverless # install serverless framework
$: sls create --template https://github.com/collierrgbsitisfise/serverless-bonk-template --path <dir name>

Next serverless plugins are used πŸ”Œ


File structure πŸ“

.
β”œβ”€β”€ resources                           # resources such as VPC, DynamoDB Tables etc.
β”œβ”€β”€ scripts                             # will be used in CI/CD, development process - should not be part of production bundle.
β”œβ”€β”€ schemas                             # schemas to validate API request on API gateway level.
β”œβ”€β”€ @mocks                              # mocks which will be used in tests/development.
β”œβ”€β”€ @types                              # types.
β”œβ”€β”€ env                                 # env files.
β”œβ”€β”€ lib                                 # helpers to operate with lambdas itself, should not be used inside lambda, should not operate somehow with business logic.
    β”œβ”€β”€ apiGatewayLambdaWrapper.ts      # wrap lambdas which are linked to api gateway.
    β”œβ”€β”€ cloudWatchLambdaWrapper.ts      # wrap lambdas which are subscribed to cloud watch event.
    β”œβ”€β”€ snsLambdaWrapper.ts             # wrap lambdas which are subscribed to sns message.
    β”œβ”€β”€ sqsLambdaWrapper.ts             # wrap lambdas which are subscribed to sqs message.
    └── dynamoDBStreamLambdaWrapper.ts  # wrap lambdas which are subscribed to dynamoDB stream.
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ functions                       # lambda fucntions.
β”‚   β”‚   β”œβ”€β”€ example
β”‚   β”‚   β”‚   β”œβ”€β”€ example.ts              # `Example` lambda source code.
β”‚   β”‚   β”‚   └── example.yaml            # `Example` function template part.
β”‚   β”‚   β”‚
β”‚   β”‚   └── index.ts                    # import/export all lambdas.
β”‚   β”‚
β”‚   β”œβ”€β”€ helpers                         # helpers which are used inside src folder.
β”‚   β”œβ”€β”€ services                        # services logic which will operate with external API/repositories, will contain domain logic.
β”‚   └── repositories                    # operate with database.
β”‚
β”œβ”€β”€ package.json
β”œβ”€β”€ serverless.ts                       # serverless service file.
β”œβ”€β”€ tsconfig.json                       # typescript compiler configuration
β”œβ”€β”€ tsconfig.paths.json                 # typescript paths
β”œβ”€β”€ webpack.config.js                   # webpack configuration
β”œβ”€β”€ .eslintrc.js                        # ESlint config
└── .prettierrc.js                      # prettier config

Scripts πŸ“œ

Command Script
Lint npm run lint
Prettier npm run prettier
Typescript check npm run ts-check
Test npm run test
Setup env ENV=<envValue> npm run setup # will create .env on root level

How to deploy πŸš€

$: ENV=<envValue> npm run setup # setup env file
$: npm run deploy # deploy

Folders purpose πŸ“‚

libs βš™οΈ

On the libs folder are defined utils which will operate with lambda itself. The libs utils should't be used inside handler. In boilerplate are predefined lambda wrappers for base case scenario lambda use:

Wrapper for lambda tied to dynamoDB stream

import { DynamoDBStreamEvent, Context, Callback } from 'aws-lambda';

export const dynamoDblambdaWrapper = (
  lambda: (event: DynamoDBStreamEvent, context: Context, callback: Callback) => Promise<any>,
  onSucces: (event: DynamoDBStreamEvent, result: any) => any | PromiseLike<any>,
  onError: (event: DynamoDBStreamEvent, error: Error) => any | PromiseLike<any>,
) => {
  return function wrapp(event: DynamoDBStreamEvent, context?: Context, callback?: Callback): Promise<any> {
    return Promise.resolve()
      .then(() => lambda(event, context, callback))
      .then((res: any) => onSucces(event, res))
      .catch((err: Error) => onError(event, err));
  };
};

Wrapper for lambda tied to ApiGateway

export type Headers = { [key: string]: string };

export type LambdaFunction = (
  event: APIGatewayEvent,
  context?: Context,
  callback?: Callback,
) => [any, number, Headers] | Promise<[any, number, Headers]>;

export type OnSuccesHandler = (
  value: any,
  statusCode: number,
  headers?: Headers,
) => APIGatewayProxyResult | PromiseLike<APIGatewayProxyResult>;

export type OnErrorHandle = (error: Error) => Promise<APIGatewayProxyResult>;

const defaultHeaders = {
  'Content-Type': 'application/json',
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Credentials': true,
};

const onSuccesHandler = (
  data: any,
  statusCode: number,
  headers?: Headers,
): APIGatewayProxyResult | PromiseLike<APIGatewayProxyResult> => ({
  statusCode,
  headers: {
    ...defaultHeaders,
    ...headers,
  },
  body: JSON.stringify(data),
});

const onErrorHandler = async (error: Error): Promise<APIGatewayProxyResult> => {
  return {
    statusCode: 500,
    headers: defaultHeaders,
    body: JSON.stringify(error),
  };
};

export const apiGatewayLambdaWrapper = (
  lambda: LambdaFunction,
  onSucces: OnSuccesHandler = onSuccesHandler,
  onError: OnErrorHandle = onErrorHandler,
) => {
  return function wrapp(event: APIGatewayEvent, context: Context, callback: Callback): Promise<APIGatewayProxyResult> {
    return Promise.resolve()
      .then(() => lambda(event, context, callback))
      .then(([res, code, headers]: [any, number, Headers]) => onSucces(res, code, headers))
      .catch(onError);
  };
};

@mocks πŸ—’οΈ

Some raw data(example of sns message, api request, sqs message, etc) which could be used during local development or for test.

@types πŸ“š

general types, which will be used across project..

env βš†

For each environment should be predefined <ENV>.env file, which will be used by setup-script before deploy.

Should't contain sensitive info such as secrets , db passwords, etc. Such kind of info must be retrived from secret-manager in runtime

resources πŸ”†

Define resources which will be created/updated on deploy, such as dynamodb table, SqlRDSInstance, etc.

schemas βœ…

Define request schemas by which ApiGateway will validate request. Also could be defined response schemas. All of them could be used in test or/and for documentation

scripts πŸ“œ

.js files which usually are used in CI/CD. Also it could be used in development purpose.

Scripts examples example:

  • setup .env variables
  • setup development webhooks using ngrok
  • adding additional .env variables on CI/CD
  • purge cloudfront cache

src πŸ—„οΈ

Internal logic of application services, repository, helpers.

There is an article which describes one of the predefined helpers

Releases

No releases published

Packages

No packages published