Looking for Senior AWS Serverless Architects & Engineers?
Let's TalkIntroduction
In this article, we will look at AWS Serverless Development (Serverless Framework + Node.js) Coding Best Practices that will help your team implement production applications code with better readability, maintenance, reusability, performance, code management, and security.
Coding best practices from the beginning of the project to production leads to a high quality application and easy maintenance far into the future.
What is Serverless on AWS?
Serverless means building and running applications without thinking about servers.
AWS offers technologies for running code, managing data, and integrating applications, all without managing servers. Serverless technologies feature automatic scaling, built-in high availability, and a pay-for-use billing model to increase agility and optimize costs. These technologies also eliminate infrastructure management tasks like capacity provisioning and patching, so you can focus on writing code that serves your customers. Serverless applications start with AWS Lambda, an event-driven compute service natively integrated with over 200 AWS services and software as a service (SaaS) applications. [1]
What Is Serverless Framework?
Serverless Framework is an open-source infrastructure-as-code (IaC) tool and development framework that allows you to build and deploy serverless applications.
What is NodeJs?
Here’s a formal definition as given on the official Node.js website:
Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine
Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.
Node.js’ package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
AWS Serverless Development - Serverless Framework Coding Best Practices
Break large serverless.yml into multiple files
When serverless.yml gets too big, you can break it up into smaller files and reference them back in the main serverless.yml. This helps to keep the serverless.yml file easily manageable.
To reference properties in other YAML files use this syntax:
in this configuration file:
Category: Code management
Benefits: Keep code clean and well manageable
Example:
The example below shows how to break large serverless.yml into multiple files
- serverless.yml
- yml/custom.yml
- yml/plugins.yml
- yml/functions.yml
One IAM role per function
By default, the Serverless Framework uses a shared role for all the functions in the serverless.yml. This too violates the principle of least privilege as functions would gain unnecessary access through the shared role.
Instead, you should use the serverless-iam-roles-per-function plugin and define IAM roles for each function.
Category: Security
Benefits: Enhance security through the principle of least privilege
Example:
No wildcards in IAM role statements
Serverless applications can be more secure than their container and VM counterparts. But, we still need to do our part and make sure we don’t overprovision our functions with access.
We should follow the principle of least privilege and grant our functions the minimal amount of access they need. And that means granting permissions to perform specific operations against specific resources.
Category: Security
Benefits: Enhance security through the principle of least privilege granting permissions to perform specific operations against specific resources.
Example:
Bad
Good
Setup a defaults file for serverless.yml imports
If there are configuration values that are specific to an environment (DEV, QA, PREPROD, PROD), consider creating a separate file that would act as a reference for injecting values into the serverless.yml file that performs the actual deployment.
Category: Code management
Benefits: Avoid code logic at the handler level to perform operations based on the environment.
Based on default values imported into the serverless.yml, you can utilize environment variables to hold only those values for that environment deployment.
Example:
Note: Place the defaults in the root directory, and manage the path reference for the defaults file from the serverless.yml accordingly
Configure stack tags
Tags help you find resources easier and you can even use them to track your AWS spending. By default, the Serverless Framework inserts the STAGE tag to the generated CloudFormation template.
However, you should consider adding other custom tags using the stackTags property. Common tags to consider include Author, Team, Feature, and Region.
Category: Resource Management
Benefits: Resource tagging helps to identify different resources on large projects & able to track AWS spending based on resource tagging.
Use webpack to improve cold start and reduce package size
For Node.js functions, the Initialization time is a big part of the cold start time and can vary greatly depending on how many dependencies you have. Webpack can help reduce Initialization time significantly. You should use the serverless-webpack plugin to run webpack automatically every time you deploy your code.
Category: Performance
Benefits: Deployments are much faster because package size is small
Reduce the number of AWS Lambda function versions using the prune plugin
Following deployment, the Serverless Framework does not purge previous versions of functions from AWS, so the number of deployed versions can grow out of hand rather quickly.
It is good practice to limit the number of Lambda versions to keep using serverless-prune-plugin.
Category: Resource Management
Example:
Store sensitive details in secrets manager/parameter store/SLS params
Hardcoding credentials, API keys, secrets as part of the environment variables in the IAC stack, leads to storing those details at the repository level. For security purposes, sensitive details can be stored in the Secrets manager (or Parameter store, depending on use case) or SLS params.
For SLS param case value show encrypted in the serverless pro dashboard but they come out plain text in the console when storing into Lambda environment variable, so that is not most secure way for sensitive information.
In the case of a secrets manager, the most secure way is to pull in using the AWS SDK inside the lambda function and not expose it at all as an environment variable as they get exposed in the lambda console.
Category: Security
Benefits: Better control since the rotation of secrets can be done by the admin from the console with no hard coding required in the repository.
AWS Serverless Development - NodeJS Coding Best Practices
Use Latest LTS versions of Node
Avoid using older versions of NodeJs as possible. AWS Lambda deprecated usage of older version before Node.js 12.x while ago, Recommended runtime version as of 01/21/2022 is Node.js 14.x.
Lambda functions running on Node.js 14 will have 2 full years of support.
Category: Performance, security, features
Benefits: Latest Node version always have more stability and new features.
Remove unused dependencies
Review carefully the dependencies file requires. Any unused dependencies should be removed.
Having too many dependencies opens you up to security risks as those packages could be exploited at some point on top of package size and cold starts.
We don't need aws-sdk in dependencies AWS Lambda already has this installed.
Category: Performance
Benefits:Reduce package size.Reduce cold start timing.
Import only what your code needs
When possible, instead of importing the whole dependency, we should require only what the code is going to use.
Category: Performance
Benefits: Reduce package size.Reduce cold start timing.
Example:
In this example, we only need the S3 client to upload an object to AWS S3. So instead of requiring the whole aws sdk, we can only require the S3 client.
Bad
Good
Function code management
A typical lambda function should not be more than 200 lines in code length performing only one singular task.Split logic into separate functions, if possible. However, in scenarios when the logic cannot be separated, move those sub-functions that perform a singular task into separate files under a “helper” folder, and restructure your handler function with the appropriate imports.
Category: Performance, maintenance, readability
Benefits: Easier to maintain. Smaller functions promote better management and pose a lower risk of failing a system because the logic is decoupled into separate functions.
Module exports definition
Place the module.exports definition at the bottom of the file with all the functions/variables you want to export.
Category: Readability, Maintenance
Example:
Bad
Good
Use async/await instead of callbacks
There are 3 ways to handle code that takes time, such as aws-sdk calls:
- callbacks
- promises
- async await
In the context of serverless backend lambda functions, we find async await can greatly increase the readability of code and help us avoid what is known in the NodeJS community as 'the pyramid of doom'.
Category: Readability, Maintenance
Use parallel execution when possible
When need to await for some functions to be executed and they don’t depend on the other functions responses, you can run them in parallel.
Category: Performance
Example:
Bad
Good
Remove unused functions
Functions that are not being used have to be removed to have a cleaner codebase.
Category: Readability, Maintenance
Use else block only if needed
Do not use unnecessary else blocks when possible.
Category: Readability, Maintenance
Example:
Bad
Good
Move env variables to the top of the file
Move all process.env.MY_VAR references to the top of the file for better visibility on what env variables are required in the file.
Category: Readability, Maintenance
Example:
Bad
Good
Move helper functions to a helper file
Any helper function that is used in multiple files, instead of redeclaring it on every function, we can create a helper file and export/require the function, removing code duplication.
Category: Reusability, Maintenance
Avoid using global variables in Lambda
Keeping track of the global state in a Lambda Function is undesirable because Lambda Functions executing one after the other will share the container originally spun up by the first Lambda execution. This means global variables will carry over to the next invocation.
If the global state is intended to keep track of state for 1 single invocation rather than multiple invocations, the behavior will be very unpredictable.
Category: Maintenance
Example:
Result global variable counter will have unpredictable results.
Use const/let instead of var
“const” and “let” provide more information about whether that variable can be reassigned or not.
- “const” is used for variables that won’t change the value.
- “let” is used for variables that will change the value
Category: Readability, Maintenance
Avoid nested checks as much as possible
Nested checks make the code more difficult to follow.
Category: Readability, Maintenance
Example:
Bad
Good
Use strict equality operator when possible (=== | !==)
The most notable difference between this operator and the equality (==) operator is that if the operands are of different types, the == operator attempts to convert them to the same type before comparing. [2] It makes code more reliable and easier to troubleshoot.
Category: Readability, Maintenance
Remove “this” keyword
Don’t use “this” when declaring a new variable.
Category: Readability
Bad
Good
Don’t use magic strings
Magic strings are rarely self-documenting. If you see one string, that tells you nothing of what else the string could / should be. You will probably have to look into the implementation to be sure you've picked the right string.When a magic string is used more than once and you have to refactor the code, you would have to update the value in several places which makes the process error-prone.
Category: Readability, Reusability, Maintenance
Example:
Bad
Good
Utilize Object Destructuring to import only needful methods
Instead of importing the entire library, we can use object destructuring to only import the methods we need.
Category: Performance
Example:
Bad
Good
The arrow function syntax is preferred
Arrow function syntax introduced in ES6 is preferred over regular function syntax for readability.
Category: Readability
Example:
Bad
Good
Conclusion
Coding best practices always matter, When a small or big team works on a project and follows coding best practices then of course produced production application quality code will have better Readability, Maintenance, Reusability, Performance, Code Management, Security.
In this blog, Covered a few above-shared generic coding best practices but there are more possible practices as well.
Coding best practices from the beginning of the project to production leads to the high quality of your application and easy maintenance in the future going.
Sources
[1] https://aws.amazon.com/serverless/
[2] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality