├── .github
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── main.yml
├── .gitignore
├── docs
└── serverless.md
├── package-lock.json
├── package.json
├── readme.md
├── renovate.json
├── serverless.yml
├── src
├── handlers
│ ├── hello-async
│ │ ├── __test__
│ │ │ ├── index_test.ts
│ │ │ └── mock_event.json
│ │ └── index.ts
│ ├── hello-kinesis-stream
│ │ ├── __test__
│ │ │ ├── index_test.ts
│ │ │ └── mock_event.json
│ │ └── index.ts
│ └── index.ts
├── helpers
│ └── kinesis-stream-helper.ts
├── index.ts
└── interfaces
│ ├── base.ts
│ ├── kinesis-stream-event.d.ts
│ └── lambda-proxy.d.ts
├── tsconfig.json
├── tsconfig.test.json
└── tslint.json
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## one sentence summary of changes
2 |
3 | #### Is it a breaking change?: NO / YES
4 |
5 |
6 | ### **Why** did you make these changes?
7 |
8 | ex) I need to access `X` filed from api
9 |
10 |
11 | ### **What** did you changed?
12 |
13 | ex) I added `X` field to `Y` entity.
14 |
15 |
16 | ### What do you especially want to get reviewed?
17 |
18 | The method of rendering `X` field
19 |
20 | ### Is there any other comments that every teammate should know?
21 |
22 | You should use `X` field instead of using `Y` field
23 |
24 |
25 | ### Submission Type
26 |
27 | * [ ] Bugfix
28 | * [ ] New Feature
29 | * [ ] Refactor
30 |
31 |
32 | ### Checklist
33 |
34 | * [ ] test for core logics?
35 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: push
3 | jobs:
4 | job:
5 | runs-on: ubuntu-latest
6 | container: node:12
7 | timeout-minutes: 15
8 | services:
9 | mysql:
10 | image: mysql:5.7
11 | env:
12 | MYSQL_ROOT_PASSWORD: root-password
13 | MYSQL_DATABASE: vingle
14 | MYSQL_USER: vingle
15 | MYSQL_PASSWORD: vingle
16 | options: >-
17 | --health-cmd "mysqladmin ping -h 127.0.0.1" --health-interval 10s --health-timeout 5s --health-retries 5
18 | postgres:
19 | image: postgres:9.4
20 | env:
21 | POSTGRES_PASSWORD: vingle
22 | options: >-
23 | --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
24 | dynamodb:
25 | image: amazon/dynamodb-local
26 | # @note Github overwrites WORKDIR to repository path, so overwrite that again
27 | options: >-
28 | --workdir /home/dynamodblocal
29 | --health-cmd "curl --fail http://127.0.0.1:8000/shell/ || exit 1" --health-interval 10s --health-timeout 5s --health-retries 5
30 | redis:
31 | image: redis
32 | options: >-
33 | --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
34 | steps:
35 | - uses: actions/checkout@v1
36 | - name: Setup
37 | run: |
38 | apt-get update
39 | apt-get install -y zip
40 | - name: Prepare
41 | env:
42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
43 | run: |
44 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
45 | npm ci
46 | - name: Lint
47 | run: npm run lint
48 | - name: Test
49 | run: npm test
50 | env:
51 | MYSQL_URL: mysql://vingle:vingle@mysql:3306/vingle
52 | POSTGRES_URL: postgres://postgres:vingle@postgres:5432/postgres
53 | DYNAMO_TYPES_ENDPOINT: http://dynamodb:8000
54 | REDIS_URL: redis://redis:6379/10
55 | # - name: Deploy
56 | # if: github.event_name == 'push' && github.ref == 'refs/heads/master'
57 | # run: npm run deploy:prod
58 | # env:
59 | # AWS_DEFAULT_REGION: us-east-1
60 | # AWS_REGION: us-east-1
61 | # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
62 | # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
63 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dst
2 | dst.zip
3 |
4 | .env
5 |
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # node-waf configuration
29 | .lock-wscript
30 |
31 | # Compiled binary addons (http://nodejs.org/api/addons.html)
32 | build/Release
33 |
34 | # Dependency directories
35 | node_modules
36 | jspm_packages
37 |
38 | # Optional npm cache directory
39 | .npm
40 |
41 | # Optional REPL history
42 | .node_repl_history
43 |
44 | .serverless
45 |
46 | # IDE Configurations
47 | .vscode
48 | .idea
49 |
--------------------------------------------------------------------------------
/docs/serverless.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/balmbees/lambda-microservice-template/d6e536b2b819a334d1921936903428fbd7f3ce29/docs/serverless.md
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lambda-microservice-template",
3 | "version": "1.0.1",
4 | "description": "Template for microservice using lambda / typescript / serverless",
5 | "main": "index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/balmbees/lambda-microservice-template.git"
9 | },
10 | "scripts": {
11 | "prebuild": "check-engine && rm -rf dst",
12 | "build": "tsc",
13 | "postbuild": "cd src && find . -name '*.json' -type f -exec cp {} ../dst/{} \\; && cd ..",
14 | "prepack": "rm -f dst.zip",
15 | "pack": "cp package.json package-lock.json dst/ && cd dst && npm ci --production && npm ls && zip -rqy ../dst.zip . && cd ..",
16 | "pretest": "npm run build -- -p ./tsconfig.test.json",
17 | "test": "mocha --exit -t 20000 dst/**/__test__/**/*.js",
18 | "lint": "tslint -c tslint.json 'src/**/*.ts'",
19 | "deploy": "npm run build && npm run pack && serverless deploy",
20 | "deploy:stage": "npm run deploy -- -s stage",
21 | "deploy:prod": "npm run deploy -- -s prod",
22 | "info:stage": "sls info -s stage",
23 | "info:prod": "sls info -s prod"
24 | },
25 | "engines": {
26 | "node": "^12.0.0",
27 | "npm": "^6.10.2"
28 | },
29 | "author": "Kurt Lee",
30 | "license": "ISC",
31 | "devDependencies": {
32 | "@types/chai": "4.2.16",
33 | "@types/mocha": "5.2.7",
34 | "@types/node": "8.10.59",
35 | "@vingle/serverless-tag-plugin": "1.1.3",
36 | "@vingle/tslint-preset": "1.0.1",
37 | "chai": "4.3.4",
38 | "check-engine": "1.10.0",
39 | "mocha": "6.2.3",
40 | "serverless": "1.83.3",
41 | "serverless-prune-plugin": "1.5.0",
42 | "tslint": "6.1.3",
43 | "typescript": "3.9.6"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Microservice Name
2 |
3 | ## Bussiness Domain
4 | Simple, Easy to read, "One Sentence" Description
5 |
6 | ```
7 | ex) User Recommendation On Vingle
8 | Feed on Vingle
9 | Interest Recommendation On Vingle
10 | ```
11 |
12 | ## Responsibility
13 | Clear and Specific description about Responsibility of the service.
14 | be mind that it should specify the "work" that could be directly understandable to developer also. such as "Rendering" / "Business Logic" / "Persistency" / "Tracking".. etc
15 |
16 | ```
17 | ex) user recommendation business logic & recommendation history logging
18 | ```
19 |
20 |
21 | ## Architecture Diagram
22 | Simple diagram representing the architecture, which showing
23 | 1. Infrastrcture resources the service is using. such as "DynamoDB" / "RDS"
24 | 2. Other microservices that the service is consuming. such as "Vingle Feed" / "Color Extractor"
25 |

26 |
27 | ## Infrastructures
28 | Link to resources that deployed. such as link to "Cloudformation" or "Lambda" or "CloudWatch"
29 |
30 |
31 | ## Usage
32 |
33 | Description about "How developer consume this service"
34 |
35 | Most commonly it will be "HTTP Restful API", so than you could just list up the APIs.
36 |
37 | ```
38 | Content-Type: 'application/json'
39 | Authorization: 'token'
40 |
41 | GET api/users/recommendations
42 | POST api/users/recommendations/:recommendationId/feedback
43 | ```
44 |
45 | but also, it could be a simple background worker such as Kinesis Stream consumer, or S3 Event handler
46 |
47 | in such case, just describe how we can "Use" this service by sending event on Kinesis Stream or S3
48 |
49 |
50 | ## Maintainer
51 |
52 | Specify who is mainly responsible for this service. meaning of responsible here is that who knows this service most, and who can help and manage other peoples to make change on the service.
53 | it could be serveral peoples also
54 |
55 | ```
56 | ex) spam-checker -> mooyeol
57 | TrackTicket -> Kurt / Shin
58 | ```
59 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "@vingle",
4 | "@vingle:nodejs12",
5 | "@vingle:lambda"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/serverless.yml:
--------------------------------------------------------------------------------
1 | service: microservice-template
2 |
3 | provider:
4 | name: aws
5 | runtime: nodejs12.x
6 | stage: ${opt:stage, 'stage'}
7 | logRetentionInDays: 30
8 |
9 | plugins:
10 | - serverless-prune-plugin
11 | - "@vingle/serverless-tag-plugin"
12 |
13 | custom:
14 | prune:
15 | automatic: true
16 | number: 5
17 |
18 | package:
19 | artifact: dst.zip
20 |
21 | functions:
22 | helloAsync:
23 | handler: handlers.helloAsync
24 | events:
25 | - http:
26 | path: hello
27 | method: get
28 |
--------------------------------------------------------------------------------
/src/handlers/hello-async/__test__/index_test.ts:
--------------------------------------------------------------------------------
1 | import { expect } from "chai";
2 |
3 | import handler from "../index";
4 | const mockEvent = require("./mock_event.json"); // tslint:disable-line
5 |
6 | describe("HelloAsync", () => {
7 | describe("handler", () => {
8 | it("should work", async () => {
9 | const res = await handler(mockEvent);
10 | expect(res.statusCode).to.be.eq(200);
11 | });
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/handlers/hello-async/__test__/mock_event.json:
--------------------------------------------------------------------------------
1 | {
2 | "resource": "some resource",
3 | "path": "path",
4 | "httpMethod": "GET",
5 | "headers": {
6 | "Accept": "application/json",
7 | "Accept-Encoding": "utf-8",
8 | "Accept-Language": "en",
9 | "CloudFront-Forwarded-Proto": "",
10 | "CloudFront-Is-Desktop-Viewer": "",
11 | "CloudFront-Is-Mobile-Viewer": "",
12 | "CloudFront-Is-SmartTV-Viewer": "",
13 | "CloudFront-Is-Tablet-Viewer": "",
14 | "CloudFront-Viewer-Country": "",
15 | "Host": "www.lvh.me:3000",
16 | "Upgrade-Insecure-Requests": "",
17 | "User-Agent": "WEBKIT/SAFARI",
18 | "Via": "Web",
19 | "X-Amz-Cf-Id": "",
20 | "X-Forwarded-For": "",
21 | "X-Forwarded-Port": "",
22 | "X-Forwarded-Proto": "",
23 | "original-uri": "www.lvh.me:3000/hello"
24 | },
25 | "queryStringParameters": {
26 | "param_1": "PARAM DATA",
27 | "param_2": "PARAM DATA"
28 | },
29 | "body": "SOME EVENT BODY"
30 | }
31 |
--------------------------------------------------------------------------------
/src/handlers/hello-async/index.ts:
--------------------------------------------------------------------------------
1 | import * as LambdaProxy from "../../interfaces/lambda-proxy";
2 |
3 | export default async function handler(event: LambdaProxy.Event) {
4 | const response = await new Promise((resolve, reject) => {
5 | setTimeout(() => {
6 | resolve({
7 | statusCode: 200,
8 | headers: {
9 | "Content-Type": "text/html",
10 | },
11 | body: `
12 |
13 |
14 | TITLE
15 |
16 |
17 | `,
18 | });
19 | }, 1);
20 | });
21 |
22 | return response;
23 | }
24 |
--------------------------------------------------------------------------------
/src/handlers/hello-kinesis-stream/__test__/index_test.ts:
--------------------------------------------------------------------------------
1 | import * as chai from "chai";
2 | chai.should();
3 |
4 | import handler from "../index";
5 | const mockEvent = require("./mock_event.json"); // tslint:disable-line
6 |
7 | describe("HelloKinesisStream", () => {
8 | describe("handler", () => {
9 | it("should work", async () => {
10 | const result = await handler(mockEvent);
11 | result.should.be.deep.eq({
12 | count: 1,
13 | payloads: [
14 | {
15 | sampleData: [
16 | 100,
17 | 200,
18 | ],
19 | },
20 | ],
21 | });
22 | });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/src/handlers/hello-kinesis-stream/__test__/mock_event.json:
--------------------------------------------------------------------------------
1 | {
2 | "Records": [{
3 | "kinesis": {
4 | "data": "eyJzYW1wbGVEYXRhIjpbMTAwLDIwMF19"
5 | }
6 | }]
7 | }
8 |
--------------------------------------------------------------------------------
/src/handlers/hello-kinesis-stream/index.ts:
--------------------------------------------------------------------------------
1 | import KinesisStreamHelper from "../../helpers/kinesis-stream-helper";
2 | import * as KinesisStreamEvent from "../../interfaces/kinesis-stream-event";
3 |
4 | export default async function handler(event: KinesisStreamEvent.Event) {
5 | const payloads = (event.Records || []).map((record) => {
6 | return KinesisStreamHelper.parseRecordData(record);
7 | });
8 |
9 | return {
10 | count: payloads.length,
11 | payloads,
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/src/handlers/index.ts:
--------------------------------------------------------------------------------
1 |
2 | // List of handlers
3 | import helloAsync from "./hello-async";
4 |
5 | const handlers = {
6 | helloAsync,
7 | };
8 |
9 | export = handlers;
10 |
--------------------------------------------------------------------------------
/src/helpers/kinesis-stream-helper.ts:
--------------------------------------------------------------------------------
1 | import * as KinesisStreamEvent from "../interfaces/kinesis-stream-event";
2 |
3 | export default class KinesisStreamHelper {
4 | public static parseRecordData(record: KinesisStreamEvent.KinesisRecord) {
5 | const payload = new Buffer(record.kinesis.data, "base64").toString("utf8");
6 | const payloadJson = JSON.parse(payload);
7 | return payloadJson;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/balmbees/lambda-microservice-template/d6e536b2b819a334d1921936903428fbd7f3ce29/src/index.ts
--------------------------------------------------------------------------------
/src/interfaces/base.ts:
--------------------------------------------------------------------------------
1 | // Event
2 | export interface Event {} // tslint:disable-line
3 | export interface Response {} // tslint:disable-line
4 |
5 | // Context
6 |
7 | // Type definitions for AWS Lambda
8 | // Definitions by: James Darbyshire (http://darb.io)
9 |
10 | /************************************************
11 | * *
12 | * AWS Lambda API *
13 | * *
14 | ************************************************/
15 |
16 | // Context
17 | // http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html
18 | export interface Context {
19 | // Properties
20 | functionName: string;
21 | functionVersion: string;
22 | invokedFunctionArn: string;
23 | memoryLimitInMB: number;
24 | awsRequestId: string;
25 | logGroupName: string;
26 | logStreamName: string;
27 | identity?: CognitoIdentity;
28 | clientContext?: ClientContext;
29 | getRemainingTimeInMillis(): number;
30 | }
31 |
32 | interface CognitoIdentity {
33 | cognito_identity_id: string;
34 | cognito_identity_pool_id: string;
35 | }
36 |
37 | interface ClientContext {
38 | client: ClientContextClient;
39 | Custom?: any;
40 | env: ClientContextEnv;
41 | }
42 |
43 | interface ClientContextClient {
44 | installation_id: string;
45 | app_title: string;
46 | app_version_name: string;
47 | app_version_code: string;
48 | app_package_name: string;
49 | }
50 |
51 | interface ClientContextEnv {
52 | platform_version: string;
53 | platform: string;
54 | make: string;
55 | model: string;
56 | locale: string;
57 | }
58 |
--------------------------------------------------------------------------------
/src/interfaces/kinesis-stream-event.d.ts:
--------------------------------------------------------------------------------
1 | import * as Base from "./base";
2 |
3 | // Event
4 | export interface Event extends Base.Event {
5 | Records?: KinesisRecord[];
6 | }
7 |
8 | export interface KinesisRecord {
9 | kinesis: {
10 | partitionKey: string;
11 | kinesisSchemaVersion: string;
12 | data: string;
13 | sequenceNumber: string;
14 | };
15 | eventSource: string;
16 | eventID: string;
17 | invokeIdentityArn: string;
18 | eventVersion: string;
19 | eventName: string;
20 | eventSourceARN: string;
21 | awsRegion: string;
22 | }
23 |
24 | // Response
25 | export interface Response extends Base.Response {
26 | [key: string]: any;
27 | }
28 |
29 | // Context
30 | export type Context = Base.Context;
31 |
--------------------------------------------------------------------------------
/src/interfaces/lambda-proxy.d.ts:
--------------------------------------------------------------------------------
1 | import * as Base from "./base";
2 |
3 | // Event
4 | export interface Event extends Base.Event {
5 | resource?: string;
6 | path?: string;
7 | httpMethod?: string;
8 | headers: EventHeaders;
9 | queryStringParameters: EventQueryStringParameters;
10 | pathParameters?: EventPathParameters;
11 | stageVariables?: EventStageVariables;
12 | requestContext?: {
13 | "accountId": string;
14 | "resourceId": string;
15 | "stage": string;
16 | "requestId": string;
17 | "identity": {
18 | "cognitoIdentityPoolId": any;
19 | "accountId": any;
20 | "cognitoIdentityId": any;
21 | "caller": any;
22 | "apiKey": any;
23 | "sourceIp": string,
24 | "accessKey": any;
25 | "cognitoAuthenticationType": any;
26 | "cognitoAuthenticationProvider": any;
27 | "userArn": any;
28 | "userAgent": string;
29 | "user": any;
30 | }
31 | "resourcePath": string;
32 | "httpMethod": string;
33 | "apiId": string;
34 | };
35 | body?: string;
36 | }
37 |
38 | export interface EventHeaders {
39 | "Accept"?: string;
40 | "Accept-Encoding"?: string;
41 | "Accept-Language"?: string;
42 | "CloudFront-Forwarded-Proto"?: string;
43 | "CloudFront-Is-Desktop-Viewer"?: string;
44 | "CloudFront-Is-Mobile-Viewer"?: string;
45 | "CloudFront-Is-SmartTV-Viewer"?: string;
46 | "CloudFront-Is-Tablet-Viewer"?: string;
47 | "CloudFront-Viewer-Country"?: string;
48 | "Host"?: string;
49 | "Upgrade-Insecure-Requests"?: string;
50 | "User-Agent"?: string;
51 | "Via"?: string;
52 | "X-Amz-Cf-Id"?: string;
53 | "X-Forwarded-For"?: string;
54 | "X-Forwarded-Port"?: string;
55 | "X-Forwarded-Proto"?: string;
56 | "original-uri"?: string;
57 | }
58 |
59 | export interface EventQueryStringParameters {
60 | [key: string]: string;
61 | }
62 |
63 | export interface EventPathParameters {
64 | [key: string]: string;
65 | }
66 |
67 | export interface EventStageVariables {
68 | [key: string]: string;
69 | }
70 |
71 | // Response
72 | export interface Response extends Base.Response {
73 | statusCode: number;
74 | headers: {
75 | "Content-Type": string,
76 | };
77 | body: string;
78 | }
79 |
80 | export type Context = Base.Context;
81 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es2017",
5 | "noImplicitAny": true,
6 | "sourceMap": true,
7 | "outDir": "dst",
8 | "strict": false,
9 | "strictNullChecks": true,
10 | "experimentalDecorators": true,
11 | "emitDecoratorMetadata": true,
12 | "lib": [
13 | "es2017"
14 | ]
15 | },
16 | "exclude": [
17 | "**/__test__/"
18 | ],
19 | "include": [
20 | "src/**/*"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es2017",
5 | "noImplicitAny": true,
6 | "sourceMap": true,
7 | "outDir": "dst",
8 | "strict": false,
9 | "strictNullChecks": true,
10 | "experimentalDecorators": true,
11 | "emitDecoratorMetadata": true,
12 | "lib": [
13 | "es2017"
14 | ]
15 | },
16 | "include": [
17 | "src/**/*"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@vingle/tslint-preset"]
3 | }
4 |
--------------------------------------------------------------------------------