Advanced Serverless Functions in JavaScript on AWS Lambda: from theory to production
\nServerless architecture based on AWS Lambda has become the standard for building microservices and event-driven systems. If you have already mastered basic functions (triggers from S3, API Gateway, DynamoDB), it's time to move to the next level. In this article, we will cover advanced techniques for working with AWS Lambda on Node.js: cold start optimization, working in VPC, using layers, proper monitoring and debugging. Each section is accompanied by practical code examples.
\n\n1. Cold Start Optimization
\nCold start is the main enemy of Serverless function performance. When a Lambda function has not been used for some time, AWS creates a new container, loads the code and initializes dependencies. This can take from 200 ms to several seconds. Let's look at how to minimize this effect.
\n\n1.1. Using Global Scope
\nCode outside the handler is executed only once when the container is created. Move heavy operations there: database connections, configuration loading, SDK initialization.
\n// ❌ Bad: each call creates a new connection\nexports.handler = async (event) => {\n const AWS = require('aws-sdk');\n const dynamoDB = new AWS.DynamoDB.DocumentClient();\n // ...\n};\n\n// ✅ Good: connection is created once\nconst AWS = require('aws-sdk');\nconst dynamoDB = new AWS.DynamoDB.DocumentClient();\n\nexports.handler = async (event) => {\n // use dynamoDB\n};\n\n1.2. Reducing Package Size
\nEvery extra module increases loading time. Use aws-sdk version 3 with tree-shaking or import only the needed clients:
// Instead of the full SDK:\nconst { DynamoDBClient } = require('@aws-sdk/client-dynamodb');\nconst { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');\n\n1.3. Provisioned Concurrency
\nFor latency-critical functions (e.g., APIs under load), enable Provisioned Concurrency. This ensures that a specified number of containers are always "hot".
\n// Example via AWS CLI:\naws lambda put-provisioned-concurrency-config \\\n --function-name my-function \\\n --qualifier PROD \\\n --provisioned-concurrent-executions 10\n\n2. Working with VPC: Security and Performance
\nWhen a Lambda function needs access to resources inside a VPC (RDS, ElastiCache, private APIs), network access configuration is required. However, connecting to a VPC increases cold start by 5-10 seconds due to ENI (Elastic Network Interface) creation.
\n\n2.1. Optimizing VPC Functions
\n- \n
- Use VPC Endpoints for AWS services (S3, DynamoDB, SQS) — this allows accessing them without going through NAT/Internet Gateway. \n
- Minimize the number of subnets — Lambda creates an ENI in each specified subnet. Limit yourself to two (different AZs). \n
- Cache connections — if the function connects to RDS, use a connection pool (e.g.,
pg-poolfor PostgreSQL). \n
const { Pool } = require('pg');\n\n// Pool is created outside the handler\nconst pool = new Pool({\n host: process.env.DB_HOST,\n database: 'mydb',\n max: 5, // connection limit\n idleTimeoutMillis: 30000,\n});\n\nexports.handler = async (event) => {\n const client = await pool.connect();\n try {\n const result = await client.query('SELECT NOW()');\n return { statusCode: 200, body: result.rows };\n } finally {\n client.release();\n }\n};\n\n2.2. VPC and Cold Start
\nIf cold start is critical, consider alternatives:
\n- \n
- Use Aurora Serverless or DynamoDB instead of RDS. \n
- Move data to ElastiCache (Redis) and connect via