/serverless-bonk-template

Serverless boilerplate based on serverless-webpack + typescript.

Primary LanguageTypeScriptMIT LicenseMIT

⚡️ 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