The project structure
. ├── README.md ├── bin │ └── www // enterpoint for the local server ├── package.json ├── serverless.yml // a configuration of Serverless Framework ├── src │ ├── config │ │ ├── config.json // a configuration of the local project │ │ └── config.ts │ ├── constant │ │ ├── app-constants.ts │ │ ├── tags.ts │ │ └── types.ts │ ├── controller // controllers based on inversify-restify-utils │ │ ├── article.controller.ts │ │ ├── home.controller.ts │ │ └── user.controller.ts │ ├── http │ │ └── response.ts │ ├── ioc // inversify │ │ ├── ioc.ts │ │ └── loader.ts │ ├── lambda.ts // enterpoint for AWS Lambda │ ├── server-base.ts // the base class for the server. base settings │ ├── server-lambda.ts // inherited from base, class for lambda │ ├── server-local.ts // inherited from base, class for local │ └── service │ ├── article.service.ts │ └── user.service.ts └── tsconfig.json We are going to compile the project in JavaScript with help of IntelliJIdea.
For that purpose edit tsconfig.js by setting compileOnSave to true
"compileOnSave": true, // required for onfly compilation "compilerOptions": { "outDir": "./dist", "baseUrl": "src", "sourceMap": true, "inlineSources": true, "declaration": false, "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "noImplicitAny": false, "target": "es6", "typeRoots": [ "node_modules/@types" ], "types": [ "reflect-metadata" ], "module": "commonjs", "lib": [ "es2017", "dom" ] } } Enable TypeScript compiler in the settings:
Add task Compile TypeScript before launching:
Create watcher for config.json. It watches for changes and copies that file to dist/config 
All previous tasks can be done by using grunt or gulp
Configure Inversion of Control. By creating ioc.ts:
import 'reflect-metadata'; // don't forget to import this import {Container, inject} from 'inversify'; import {autoProvide, makeProvideDecorator, makeFluentProvideDecorator} from 'inversify-binding-decorators'; import {makeLoggerMiddleware} from 'inversify-logger-middleware'; let container = new Container(); if (process.env.NODE_ENV === 'development') { // for logging of injections // let logger = makeLoggerMiddleware(); // container.applyMiddleware(logger); } let provide = makeProvideDecorator(container); let fluentProvider = makeFluentProvideDecorator(container); let provideSingleton = function(identifier) { // the annotation for providing singleton return fluentProvider(identifier) .inSingletonScope() .done(); }; let provideNamed = function (identifier, name) { // the annotation for providing by name return fluentProvider(identifier) .inSingletonScope() .whenTargetNamed(name) .done(); }; let bindDependencies = function (func, dependencies) { // for a function injections. e.g. func = bindDependencies(myFunc, [TYPES.MyService]); func(); let injections = dependencies.map((dependency) => { return container.get(dependency); }); return func.bind(func, ...injections); }; export {container, autoProvide, provide, provideNamed, provideSingleton, inject, bindDependencies};Import all dependencies in loader.ts:
import '../controller/home.controller'; import '../controller/user.controller'; import '../controller/article.controller'; import '../service/user.service'; import '../service/article.service';Let's configure server
Configuration:
config(app) { // configure cors app.use(restify.CORS({ origins: nconf.get("server:origins"), // defaults to ['*'] credentials: false, // defaults to false })); // to get query params in req.query app.use(restify.acceptParser(app.acceptable)); // to get passed json in req.body app.use(restify.bodyParser()); // error handler app.on('error', (error) => { this.onError(error); }); // process exceptions app.on('uncaughtException', function (request, response, route, error) { console.error(error.stack); response.send(error); }); // audit logger app.on('after', restify.auditLogger({ log: this.logger })); app.use(helmet()); // прячем некоторые заголовки вроде X-Powered-By }Place the server in the inversify container:
bootstrap(): restify.Server { super.bootstrap(); //create restify application this.app = new InversifyRestifyServer(container, { name: AppConstants.APP_NAME, version: nconf.get("server:api_version"), log: this.logger }).setConfig((app) => { this.config(app); this.listen(app); }).build(); }Let's create the UserController controller:
@Controller('/users') // annotation from inversify-restify-utils @provideNamed(TYPE.Controller, TAGS.UserController) // inject by name export class UserController implements interfaces.Controller { constructor(@inject(TYPES.UserService) private userService: UserService) { // injecting services } @Post('/register') // annotate method as the post request register(req: restify.Request, res: restify.Response, next: restify.Next) { this.userService.register(req.body.given_name, req.body.email, req.body.password, (err, user) => { if (err) { console.error(err.message); return res.json(new Response(false, err)); } res.json(new Response(true, 'User was created', user)); next(); }); } }Let's create the UserService:
@provideSingleton(TYPES.UserService) // inject as singleton export class UserService { constructor() { } register(given_name: string, email: string, password: string, callback: (error, response) => any) { const params = { ClientId: nconf.get('aws:cognito:user_pool_client_id'), Username: email, Password: password, UserAttributes: [ {Name: "email", Value: email}, {Name: "given_name", Value: given_name} ] }; console.log(params); const cognitoidentityserviceprovider = new CognitoIdentityServiceProvider(UserService.getAWSRegion()); cognitoidentityserviceprovider.signUp(params, callback); } }Let's look into the working with DynamoDB
Create table:
const AWS = require("aws-sdk"); AWS.config.update({ region: "us-west-2", endpoint: "http://localhost:8000" }); const dynamodb = new AWS.DynamoDB(); let params = { TableName : "Article" }; dynamodb.deleteTable(params, function(err, data) { if (err) { console.error("Unable to delete table. Error JSON:", JSON.stringify(err, null, 2)); } else { console.log("Deleted table. Table description JSON:", JSON.stringify(data, null, 2)); } }); params = { TableName : "Article", KeySchema: [ { AttributeName: "id", KeyType: "HASH"}, //Partition key { AttributeName: "slug", KeyType: "RANGE" } //Sort key ], AttributeDefinitions: [ { AttributeName: "id", AttributeType: "N" }, { AttributeName: "slug", AttributeType: "S" } ], ProvisionedThroughput: { ReadCapacityUnits: 1, WriteCapacityUnits: 1 } }; dynamodb.createTable(params, function(err, data) { if (err) { console.error("Unable to create table. Error JSON:", JSON.stringify(err, null, 2)); } else { console.log("Created table. Table description JSON:", JSON.stringify(data, null, 2)); } });Insert a record in the table:
createArticle(body: any, cb: (newArticle) => any, err) { const key = { slug: body.slug, title: body.title }; const value = { content: body.content }; this.Article.insert(key, value).exec() .then(cb) .catch(err); }Deployment
Before deployment you need to configure zipping of node_modules and dist folders. There is an example of External Toole 
Then add this tool to the tasks before launching: 
Install required packages:
npm iDeploy packages by running
npm run sls-deployWarning
To get it working with aws-serverless-express you need to apply this patch

