Create a Clean Architecture REST API with Serverless Framework and TypeScript

This article is a translation of the original article.

I implemented an empty API of sample prepared in the previous article*1 so that it works.

Specification of API

I created todo's REST API.

  • POST /todos : Register todo
  • GET /todos : List todos
  • GET /todos/:id : Get todo
  • PUT /todos/:id : Update todo
  • DELETE /todos/:id : Delete todo

Architecture

Final goal (but I can not get it...)

I tried to make it to Clean Architecture.

Clean Architecture is an architecture that attempts to improve maintainability by separating abstract implementations (business logic etc.) from concrete implementation (framework, DB, etc.) as follows.

  • Divide the system into four layers (Entities, Use Cases, Interface Adapters, Frameworks and Drivers)
  • Make dependency between layers one-way from concrete layer to abstract layer (Frameworks and Drivers -> Interface Adapters -> Use Cases -> Entities)

If the dependencies between the layers do not match the order of the calls, it is necessary to adjust the direction of dependency between the layers by making use of the dependency inversion principle.

The figure is easier to understand than the explanation. (The figure quoted The Clean Architecture.)

f:id:nihma:20180917114525j:plain

This article

In the architecture design of this article, I did not follow all the principles of Clean Architecture, but we adopted only the purpose / way of thinking that improves the maintainability of the abstract layer by one way from concrete layer to abstract layer. Because if follow all principles that implementation will increase too much and disadvantage will increase.

Specifically, I broke the rules as follows and obeyed the principle only dependency between layers one-way from Interface Adapters, Frameworks and Drivers to Entities and Use Cases.

  • Interface Adapters is a layer that maps concrete layers (Frameworks and Drivers) and abstract layers (Entities and Use Cases). And this layer may depend on all layers.

f:id:nihma:20180917114858p:plain

The direction of the arrow between Interface Adapters andUse Cases is all inward.

You can probably reuse business logic even if you want to change Framework, DB etc.

Implementation

I uploaded the code to GitHub.

The directory structure is as follows.

$ tree -ad -I '.git'
.
├── .circleci ... config of CircleCI
├── app       ... application codes
│   ├── adapters    ... layer of Interface Adapters
│   │   ├── commands
│   │   ├── databases
│   │   └── http
│   │       ├── requests
│   │       └── responses
│   ├── commands    ... layer of Frameworks and Drivers(command)
│   ├── databases   ... layer of Frameworks and Drivers(DynamoDB)
│   ├── entities    ... layer of Entities
│   ├── http        ... layer of Frameworks and Drivers(Serverless Framework)
│   │   ├── controllers
│   │   ├── middlewares
│   │   ├── models
│   │   ├── utils
│   │   └── views
│   ├── providers   ... setting of DI (inversify)
│   └── usecases    ... layer of Use Cases
│       ├── implementations
│       ├── inputs
│       ├── outputs
│       └── stores
├── dockers    ... config of docker
│   ├── dynamodb
│   └── serverless
└── test       ... test codes
    ├── units
    │   ├── http
    │   │   ├── controllers
    │   │   └── middlewares
    │   └── usecases
    └── utils

33 directories

In implementation of this article, I used middy for http's middleware engine. The middleware directory is app/http/middlewares/.

aws-serverless-express and serverless-http were also candidates. However, I tried using middy which is specialized in middleware so as not to use two routing frameworks of Serverless Framework and Express. middy is still alpha version so it may not work with production code, but I felt it was fairly easy to use.

Also, by checking with tslint, we made it comply with tslint-config-standard. And I tried to write a unit test with mocha and keep it clean.

How to use

There are two ways to run it in the local environment using docker or to run it with AWS.

Using docker

Clone the Repository:

$ git clone -b v0.0.1 https://github.com/nihemak/aws-sls-spa-sample-api.git sample-spa-api
$ cd sample-spa-api

Build docker environment and Reset DynamoDB Local tables:

$ npm run docker-build
$ npm run docker-up-dev
$ npm run docker-reset-tables
$ npm run docker-down

Invoke serverless-offline in docker environment:

$ npm run docker-up
$ npm run docker-offline

Try the APIs:

# POST /todos
$ curl -X POST http://localhost:3000/v1/todos -H "Content-Type: application/json" --data '{ "text": "foo" }'
# GET /todos
$ curl http://localhost:3000/v1/todos
# GET /todos/:id
$ curl http://localhost:3000/v1/todos/:id
# PUT /todos/:id
$ curl -X PUT http://localhost:3000/v1/todos/:id -H "Content-Type: application/json" --data '{ "text": "bar", "checked": true }'
# DELETE /todos/:id
$ curl -X DELETE http://localhost:3000/v1/todos/:id

Access to DynamoDB Local admin web:

http://localhost:8000/

With AWS

It is as the How to use of the previous article.

But the implementation of this article is tag v.0.0.1. Therefore, you need to change only the tag part.

Specifically, the place to Clone the Repository and Push to the AWS CodeCommit Repository will change to the following.

# spa infra repository...
$ git clone -b v0.0.1 https://github.com/nihemak/aws-sls-spa-sample-terraform.git sample-spa-infra
$ cd sample-spa-infra
$ git checkout -b master
$ git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/foobar-sample-spa-infra
$ cd ..
# spa api repository...
$ git clone -b v0.0.1 https://github.com/nihemak/aws-sls-spa-sample-api.git sample-spa-api
$ cd sample-spa-api
$ git checkout -b master
$ git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/foobar-sample-spa-api
$ cd ..
# spa web repository...
$ git clone -b v0.0.0 https://github.com/nihemak/aws-sls-spa-sample-web.git sample-spa-web
$ cd sample-spa-web
$ git checkout -b master
$ git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/foobar-sample-spa-web
$ cd ..

The repositories of aws-sls-spa-sample-terraform.git and aws-sls-spa-sample-api.git will be v0.0.1.

Summary

In this article, I made a sample REST API to manage Todo with Serverless Framework and TypeScript.

  • I was able to understand the concept and implementation cost of Clean Architecture.
  • I was able to learn TypeScript coding.

*1:This is a Japanese article. I used Terraform to code AWS 'serverless service infrastructure construction.