Sample serverless API that performs username and password authentication. Passwords are hashed and salted using node's built in crypto lib. The serverless config will deploy the entire stack including subnets, gateways, and rds. For API documentation refer to the ApiSpec.json file. You can use https://editor.swagger.io/ to view the file.
During development I did discover two things regarding TypeORM:
- typeorm/typeorm#3427 - It appears that TypeORM needs some special connection handling in Lambda. This was a real pain to track down and it's not top line in their documentation. I also added a comment in the code. Better documentation on connection handling would have saved me several hours here.
- TypeORM seems to fail silently when data type overflows column definition. Try the following cURL request a few times in a row:
curl --header "Content-Type: application/json" \
--request POST \
--data '{"user":{"firstName": "Dorian", "lastName": "Smiley", "email": "[email protected]", "username": "testUser_007867352595040011", "password": "password"}}' \
https://umen1vjrng.execute-api.us-west-2.amazonaws.com:443/dev/v1/users
The user appears to be added despite having the same username. The username is 27 characters but the limit is 26. Now remove any character from the username and repeat the test. Attempts to add the user multiple times now fail. I did not try to implement a unique constraint on the column. As far as I can tell the find method is failing silently and not adding the user.
# Get session
curl --header "Content-Type: application/json" \
--request POST \
--data '{"user":{"username":"dsmiley","password":"password"}}' \
https://umen1vjrng.execute-api.us-west-2.amazonaws.com:443/dev/v1/users/sessions
# Register User
curl --header "Content-Type: application/json" \
--request POST \
--data '{"user":{"firstName": "Dorian", "lastName": "Smiley", "email": "[email protected]", "username": "dsmiley", "password": "password"}}' \
https://umen1vjrng.execute-api.us-west-2.amazonaws.com:443/dev/v1/users
Some developers ask why the use of http-serverless which wraps express APIs. Using http-serverless makes the API more portable. You can deploy the API as either a docker container or serverless application using API Gateway and Lambda. The downside however are somewhat bloated lambda function functions. I prefer this approach though as it can give you more deployment options.
I opted to encapsulate TypeORM repository operations in a DAO and abstract the implementation to a factory. This allows for a clean migration path away from TypeORM and abstracts away persistence details. The DAO API can remain consistent regardless of the driver implementation.
- Network setup https://docs.aws.amazon.com/codebuild/latest/userguide/cloudformation-vpc-template.html
- Error resolution: CloudWatch Logs role ARN must be set in account settings to enable logging https://gist.github.com/villasv/4f5b62a772abe2c06525356f80299048
- RDS setup https://forum.serverless.com/t/create-a-resource-in-a-vpc-aws-rds/6361/5
- Dynimac host and port resoltuion arabold/serverless-export-env#8
Make sure node is installed.
Global npm packages are required
npm install grunt serverless -g
Important!: Make sure you are on the same nodejs runtime as lambda. Currently its on v8.10.
The following environment variables are required by the functions.
# Local Testing
export TEST_API_GATEWAY_HOST=localhost
export TEST_API_GATEWAY_PORT=3000
export TEST_API_PROTOCOL=http:
export DB_HOST=localhost,
export DB_PORT=3306,
export DB_NAME=test,
export DB_USER=<username>,
export DB_PWD=<password>,
export DB_TYPE=mysql
# AWS Integration Testing
export TEST_API_GATEWAY_HOST=<output endpoint>
export TEST_API_GATEWAY_PORT=443
export TEST_API_PROTOCOL=https:
export DB_HOST=10.0.0.0
export DB_PORT=3306,
export DB_NAME=test,
export DB_USER=<username>,
export DB_PWD=<password>,
export DB_TYPE=mysql
To build the app run. Note the test command will run this for you.
npm run build
Make sure you have the environment variables setup from the previous step.
To run all tests locally
export TEST_API_GATEWAY_HOST=localhost
export TEST_API_GATEWAY_PORT=3000
export TEST_API_PROTOCOL=http:
export DB_HOST=localhost
export DB_PORT=3306
export DB_NAME=test
export DB_USER=<username>
export DB_PWD=<password>
export DB_TYPE=mysql
export ALERT_EMAIL=<email>
export STAGE=local
npm test
To run integration tests after deployment
export TEST_API_GATEWAY_HOST=<service endpoint, for example umen1vjrng.execute-api.us-west-2.amazonaws.com>
export TEST_API_GATEWAY_PORT=443
export TEST_API_PROTOCOL=https:
# we unset DB_HOST and DB_PORT because the ENV vars are preffered in environment config
# we want to use stack ouputs
unset DB_HOST
unset DB_PORT
export DB_NAME=test
export DB_USER=<username>
export DB_PWD=<password>
export DB_TYPE=mysql
export ALERT_EMAIL=<email>
export STAGE=dev
npm test
Serverless can emulate a webserver and allow you to hit the gateway function using curl or Postman
serverless offline start --stage local \
--host $TEST_API_GATEWAY_HOST \
--port $TEST_API_GATEWAY_PORT \
--DbHost $DB_HOST \
--DbPort $DB_PORT \
--DbUser $DB_USER \
--DbPwd $DB_PWD \
--alert-email $ALERT_EMAIL
Your service will be accessible on localhost:3000
.
We deploy to AWS using serverless. You will need the AWS Credentials setup on your machine with the appropriate permissions.
# we unset DB_HOST and DB_PORT because the ENV vars are preffered in environment config
# we want to use stack ouputs
unset DB_HOST
unset DB_PORT
serverless deploy -v --force --stage $STAGE \
--alert-email $ALERT_EMAIL \
--DbUser $DB_USER \
--DbPwd $DB_PWD
We create the deployment pacakge using serverless. You will need the AWS Credentials setup on your machine with the appropriate permissions.
# we unset DB_HOST and DB_PORT because the ENV vars are preffered in environment config
# we want to use stack ouputs
unset DB_HOST
unset DB_PORT
serverless package -v --force --stage $STAGE \
--alert-email $ALERT_EMAIL \
--DbUser $DB_USER \
--DbPwd $DB_PWD
Serverless will deploy the following:
- API Gateway setup
- Lambda Function triggered by API Gateway
- IAM roles and permissions
- Cloudwatch Alarms
- VPS with two public subnets and two private subnets with nat gateway
- RDS instance using mysql
To take down a deployed stack, you can use the remove
command with a stage name.
While at the root of the serverless application, run:
serverless remove --stage <stage_name>