Serverless computing allows you to build applications without managing infrastructure. This guide covers developing, deploying, and optimizing Node.js functions in serverless environments.
1. Serverless Fundamentals
Key Characteristics
- No server management
- Automatic scaling
- Pay-per-execution pricing
- Event-driven execution
- Stateless by design
Use Cases
- APIs and microservices
- File processing pipelines
- Scheduled tasks
- Real-time stream processing
- Chatbots and webhooks
2. AWS Lambda Basics
Basic Function Structure
exports.handler = async (event, context) => {
try {
// Process event
const result = await processEvent(event);
// Return response
return {
statusCode: 200,
body: JSON.stringify(result),
headers: {
'Content-Type': 'application/json'
}
};
} catch (err) {
console.error('Handler error:', err);
return {
statusCode: 500,
body: JSON.stringify({ error: err.message })
};
}
};
Common Trigger Events
- API Gateway: HTTP requests
- S3: File uploads
- DynamoDB: Database changes
- SQS/SNS: Message queues
- CloudWatch: Scheduled events
3. Development Tools
Serverless Framework
# serverless.yml
service: my-serverless-app
provider:
name: aws
runtime: nodejs14.x
region: us-east-1
functions:
hello:
handler: handler.hello
events:
- http:
path: hello
method: get
# Deploy
serverless deploy
AWS SAM
# template.yaml
Resources:
HelloFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello-world/
Handler: app.lambdaHandler
Runtime: nodejs14.x
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
# Build and deploy
sam build
sam deploy --guided
4. Performance Optimization
Cold Start Mitigation
- Keep functions small (~50MB)
- Use provisioned concurrency
- Initialize connections outside handler
- Use ARM architecture (Graviton2)
- Avoid monolithic functions
Connection Pooling
const { MongoClient } = require('mongodb');
// Initialize outside handler
let client;
const connect = async () => {
if (!client) {
client = new MongoClient(process.env.DB_URI);
await client.connect();
}
return client;
};
exports.handler = async (event) => {
const db = (await connect()).db();
// Use db connection
};
5. Advanced Patterns
Function Composition
// Chaining functions with Step Functions
{
"Comment": "Order processing workflow",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:validateOrder",
"Next": "ProcessPayment"
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:processPayment",
"Next": "FulfillOrder"
}
}
}
Event Bridge Architecture
// serverless.yml
functions:
processOrder:
handler: handlers.processOrder
events:
- eventBridge:
pattern:
source: ["myapp.orders"]
detail-type: ["OrderCreated"]
// Emit custom event
const AWS = require('aws-sdk');
const eventBridge = new AWS.EventBridge();
await eventBridge.putEvents({
Entries: [{
Source: 'myapp.orders',
DetailType: 'OrderCreated',
Detail: JSON.stringify(order),
EventBusName: 'default'
}]
}).promise();
6. Testing Strategies
Local Testing
// Using serverless-offline
# Install plugin
npm install serverless-offline --save-dev
# serverless.yml
plugins:
- serverless-offline
# Run locally
serverless offline start
# Test with curl
curl http://localhost:3000/dev/hello
Mock AWS Services
// Using aws-sdk-mock
const AWS = require('aws-sdk-mock');
const { handler } = require('../handler');
test('processes S3 event', async () => {
AWS.mock('S3', 'getObject', {
Body: Buffer.from('test file content')
});
const event = { /* mock S3 event */ };
const result = await handler(event);
expect(result).toEqual({ processed: true });
AWS.restore('S3');
});
7. Production Best Practices
Security Considerations
- Least privilege IAM roles
- Environment variables for secrets
- VPC configuration for private resources
- Regular dependency updates
Monitoring and Debugging
- CloudWatch Logs and Metrics
- X-Ray for distributed tracing
- Custom business metrics
- Alerting on errors and throttles
Next: Security Best Practices →
Serverless Checklist
- ✅ Keep functions small and focused
- ✅ Initialize connections outside handler
- ✅ Set proper memory and timeout values
- ✅ Implement proper error handling
- ✅ Monitor cold start percentage
- ✅ Secure all environment variables