This project is a simple, lean and yet professional-grade implementation for a simple Create / Update / Retrieve / Delete REST API, using typescript 5, middlewares, offline dry-running and test automation and other best practices.
It's goal is to serve as a learning reference and template.
- Serverless Framework
- Jest.js for test automation
- Prettier and ESLint for code inspection and formatting
- Husky to define git hooks and impose unit test execution and linter checks before pushing
- middy middleware engine for AWS lambda. It is used to check request/response scheme, resolve application exceptions to error responses and fetch configuration from SSM, and many other handy features. Never dive into the serverless sea without it =).
- AWS SDK for Javascript v3
- Optimistic lock write verification
- (TODO) Input schema validation
- (TODO) Parameter resolution at runtime
- Node 20
- JRE installed and 'java' in the path (in order to run dynamodb local for the e2e local tests)
- AWS Account setup
- Clone the project and install local dependencies with npm
npm install
- Install a local instance of dynamodb using the serverless-dynamodb plugin (https://www.npmjs.com/package/serverless-dynamodb). You can do it by executing the script 'dynamodb:install' defined in package.json as follows.
This step is useful to validate the serverless.yml and dynamo table definitions. It also enables us to run end-to-end integration tests, with seeded test data.
npm run dynamodb:install
Note You must have a JRE installed and have java in your path to execute dynamodb local.
- Start the serverless offline. This command will also start the Dynamodb local instance automatically.
npm run offline:start
You may execute all integration and unit tests locally by running:
npm test
Note All integration tests ('test/integration/*/') will fail if serverless offline (with dynamodb offline) is not started.
To run only the unit tests there is no need to execute previous steps 1 (install local dynamodb) nor 2 (start serverless offline) by running:
npm run test:unit
Deploying to the cloud is very simple, you can use serverless directly, providing the environment name as folows:
npx serverless deploy --stage <stage_name>
- I would certainly rely on more regression tests scenarios for a real application, production code.
- Not to use DDD was a design decision, I decided to focus on structure, SOLID and clean practices.
- Here, the handler depends directly on the database layer. In (a bit) more complex scenarios, I would avoid leaving business rules in the handlers. Have a service layer to encapsulate business rules. The handler layer should encapsulates API contract rules.
- I decided not to create a single, idempotent endpoint for updating and creating (PUT) for simplicity, although I admit it is not a good REST modeling practice for entities. Different approaches like non-idempotent PUT, PATCHing attributes, etc exist, depending on many factors.
- All modules in 'lib' packages would be reused by many services, and in production code, should be extracted into one or many managed dependencies (something like '@my_company/lambda-utils') in a private dependency repository or available in a managed lambda layer (see https://docs.aws.amazon.com/lambda/latest/dg/chapter-layers.html).
- Avoid creating a single partition GSI for pagination using a smarter approach like this: https://theburningmonk.com/2018/02/guys-were-doing-pagination-wrong/.
- Define IAM role per function, adopting a more strict read/write permission policy.
- More tests scenarios are needed to achieve 100% coverage; e2e tests scenarios are meant to be less profuse, but are also missing some more scenarios.
- DynamoRepository.query will break for large datasets; We must query iteratively from an offset and make it a generative function or make it return a stream somehow to avoid memory overflow.
- Got an issue when using serverless offline and trying to resolve ${aws:accountId}, so I had to hardcode the accountId in serverless.yml. Needs attention.