Serverless Development

AWS Serverless Development: Coding Best Practices

(Serverless Framework + NodeJS)

AWS Serverless Development: Coding Best Practices

By
Jaymit Bhoraniya
February 18, 2022

Introduction

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:

  
${file(./fileName.yml):someProperty} 
  

in this configuration file:

  
serverless.yml
  

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
serverless.yml
yml/custom.yml
yml/plugin.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:

defaults.yml
serverless.yml

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