Serverless functions in TypeScript: advanced level in AWS Lambda
\n\nServerless architecture has become the de facto standard for building scalable and cost-effective applications. AWS Lambda is one of the most popular services for implementing serverless functions. If the basic syntax and triggers (API Gateway, S3, DynamoDB) are already mastered, it's time for advanced techniques. In this article, we will look at key aspects of professional Lambda function development in TypeScript: from cold start optimization to VPC integration and the use of complex patterns.
\n\n1. Cold start optimization and memory management
\n\nCold start is the main enemy of serverless application performance. In TypeScript, this problem is exacerbated by the compilation stage. Let's consider three main methods of dealing with it.
\n\n1.1. Reducing the deployment package size
\nUse esbuild for building instead of the standard tsc. It performs tree-shaking and minification, which reduces the bundle size by 5-10 times. Example configuration for serverless.yml:
plugins:\n - serverless-esbuild\ncustom:\n esbuild:\n bundle: true\n minify: true\n sourcemap: false\n target: 'node18'\n exclude: ['aws-sdk'] # SDK is already in the Lambda environment\n\n1.2. Using Provisioned Concurrency
\nFor latency-critical functions (e.g., processing API requests), configure Provisioned Concurrency. This keeps a specified number of "warm" containers constantly running:
\n\nfunctions:\n apiHandler:\n handler: src/handler.apiHandler\n provisionedConcurrency: 5 # Always 5 ready instances\n\n1.3. Optimizing imports and code
\nAvoid "heavy" libraries inside the function. For example, instead of importing the entire aws-sdk v3, import only the necessary clients:
// Bad: import * as AWS from 'aws-sdk';\n// Good:\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb';\nimport { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';\n\n2. Working with Lambda Layers and dependencies
\n\nLambda Layers allow you to extract common code and dependencies (e.g., ORM, validators) outside of the function. This speeds up deployment and reduces the size of each individual package.
\n\n2.1. Creating a layer with shared utilities
\nThe project structure might look like this:
\n\nproject/\n├── layers/\n│ └── common/\n│ └── nodejs/\n│ ├── package.json\n│ └── node_modules/\n├── functions/\n│ ├── createUser.ts\n│ └── getUser.ts\n└── serverless.yml\n\nIn serverless.yml we connect the layer:
layers:\n commonLibs:\n path: layers/common\n compatibleRuntimes:\n - nodejs18.x\nfunctions:\n createUser:\n handler: functions/createUser.handler\n layers:\n - { Ref: CommonLibsLambdaLayer }\n\n2.2. Typing for layers
\nTo make TypeScript "see" the types from the layer, create a tsconfig.json with paths:
{\n "compilerOptions": {\n "paths": {\n "@common/*": ["layers/common/nodejs/*"]\n }\n }\n}\n\nNow in the function you can write:
\nimport { validateEmail } from '@common/validators';\n\n3. Working with VPC and security
\n\nLambda functions running inside a VPC lose access to the public internet (unless a NAT Gateway is configured). This is critical for connecting to RDS, ElastiCache, or private APIs.
\n\n3.1. Configuring Lambda in a VPC
\nConfiguration via serverless.yml:
provider:\n vpc:\n securityGroupIds:\n - sg-12345\n subnetIds:\n - subnet-abc\n - subnet-def\n\nfunctions:\n dbHandler:\n handler: src/db.handler\n vpc: true # inherits provider settings\n\n3.2. "Thin Handler" pattern for connections
\nRe