Skip to content

Serverless computing enables building scalable applications without managing infrastructure. This guide explores common serverless patterns and best practices.

Core Concepts

Function as a Service (FaaS)

  • Event-driven execution
  • Automatic scaling
  • Pay per execution
  • No server management
  • AWS Lambda
  • Azure Functions
  • Google Cloud Functions
  • Cloudflare Workers

Common Patterns

API Gateway Pattern

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// AWS Lambda + API Gateway
exports.handler = async (event) => {
    const { httpMethod, path, body } = event;
    
    if (httpMethod === 'GET' && path === '/users') {
        const users = await getUsers();
        return {
            statusCode: 200,
            body: JSON.stringify(users)
        };
    }
    
    return { statusCode: 404, body: 'Not Found' };
};

Event Processing

1
2
3
4
5
6
7
8
# AWS Lambda triggered by S3 upload
def lambda_handler(event, context):
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key = record['s3']['object']['key']
        
        # Process uploaded file
        process_image(bucket, key)

Queue Processing

1
2
3
4
5
6
7
// SQS Queue processor
exports.handler = async (event) => {
    for (const record of event.Records) {
        const message = JSON.parse(record.body);
        await processMessage(message);
    }
};

Scheduled Tasks

1
2
3
4
5
6
# Serverless Framework
functions:
  cronJob:
    handler: handler.run
    events:
      - schedule: rate(1 hour)

Best Practices

Cold Start Optimization

1
2
3
4
5
6
7
8
9
// Keep connections warm
let dbConnection;

exports.handler = async (event) => {
    if (!dbConnection) {
        dbConnection = await createConnection();
    }
    return queryDatabase(dbConnection, event);
};

Error Handling

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import logging
logger = logging.getLogger()

def lambda_handler(event, context):
    try:
        result = process_event(event)
        return {'statusCode': 200, 'body': result}
    except Exception as e:
        logger.error(f"Error: {str(e)}")
        return {'statusCode': 500, 'body': 'Internal Error'}

Environment Variables

1
2
3
4
5
const config = {
    dbHost: process.env.DB_HOST,
    apiKey: process.env.API_KEY,
    region: process.env.AWS_REGION
};

Security

IAM Roles

1
2
3
4
5
6
7
8
9
provider:
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:Query
            - dynamodb:GetItem
          Resource: arn:aws:dynamodb:*:*:table/Users

Secrets Management

1
2
3
4
5
6
7
8
9
const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager();

async function getSecret(secretName) {
    const data = await secretsManager
        .getSecretValue({ SecretId: secretName })
        .promise();
    return JSON.parse(data.SecretString);
}

Monitoring

Logging

1
2
3
4
5
6
7
import json

def lambda_handler(event, context):
    print(json.dumps({
        'event': event,
        'requestId': context.request_id
    }))

Tracing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const AWSXRay = require('aws-xray-sdk-core');
const AWS = AWSXRay.captureAWS(require('aws-sdk'));

exports.handler = async (event) => {
    const segment = AWSXRay.getSegment();
    const subsegment = segment.addNewSubsegment('custom-operation');
    
    await performOperation();
    
    subsegment.close();
};

Cost Optimization

  1. Right-size memory allocation
  2. Minimize cold starts
  3. Use reserved concurrency wisely
  4. Implement timeouts
  5. Clean up unused functions
  6. Monitor invocation patterns

Testing

Unit Tests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const { handler } = require('./index');

test('returns 200 for valid request', async () => {
    const event = {
        httpMethod: 'GET',
        path: '/users'
    };
    
    const result = await handler(event);
    expect(result.statusCode).toBe(200);
});

Integration Tests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import boto3
import json

def test_lambda_integration():
    lambda_client = boto3.client('lambda')
    response = lambda_client.invoke(
        FunctionName='my-function',
        Payload=json.dumps({'test': 'data'})
    )
    assert response['StatusCode'] == 200

Deployment

Infrastructure as Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Serverless Framework
service: my-api
provider:
  name: aws
  runtime: nodejs18.x

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get

CI/CD

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# GitHub Actions
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm install -g serverless
      - run: serverless deploy

Challenges

  • Cold start latency
  • Debugging complexity
  • Vendor lock-in
  • Timeout limitations
  • Stateless constraints

Conclusion

Serverless architecture offers significant benefits for scalable, event-driven applications. Understanding patterns and best practices is key to successful implementation.