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 i
Deploy packages by running
npm run sls-deploy
Warning
To get it working with aws-serverless-express
you need to apply this patch