Skip to content

Serverless and Function as a Service (FaaS)

What is Serverless?

Serverless computing is a cloud computing execution model where the cloud provider dynamically manages the allocation and provisioning of servers. Developers write and deploy code without worrying about the underlying infrastructure.

Key Characteristics

  • No server management: Infrastructure is completely managed by the cloud provider
  • Event-driven: Functions are triggered by events (HTTP requests, database changes, file uploads, etc.)
  • Pay-per-execution: You only pay for the compute time you consume
  • Automatic scaling: Functions scale automatically based on demand
  • Stateless: Each function execution is independent

AWS Lambda

AWS Lambda is Amazon's serverless compute service that runs your code in response to events.

Basic Lambda Function

javascript
// Node.js Lambda function
exports.handler = async (event, context) => {
    console.log('Event:', JSON.stringify(event, null, 2));
    
    const { name, age } = JSON.parse(event.body);
    
    if (!name || !age) {
        return {
            statusCode: 400,
            headers: {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
            },
            body: JSON.stringify({
                error: 'Name and age are required'
            })
        };
    }
    
    const response = {
        message: `Hello ${name}, you are ${age} years old!`,
        requestId: context.awsRequestId,
        timestamp: new Date().toISOString()
    };
    
    return {
        statusCode: 200,
        headers: {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        body: JSON.stringify(response)
    };
};

Python Lambda Function

python
import json
import logging
from datetime import datetime

# Set up logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    """
    AWS Lambda handler function
    """
    logger.info(f"Received event: {json.dumps(event)}")
    
    try:
        # Parse the request body
        if 'body' in event:
            body = json.loads(event['body'])
        else:
            body = event
            
        name = body.get('name')
        age = body.get('age')
        
        # Validate input
        if not name or not age:
            return {
                'statusCode': 400,
                'headers': {
                    'Content-Type': 'application/json',
                    'Access-Control-Allow-Origin': '*'
                },
                'body': json.dumps({
                    'error': 'Name and age are required'
                })
            }
        
        # Process the request
        response_data = {
            'message': f'Hello {name}, you are {age} years old!',
            'requestId': context.aws_request_id,
            'timestamp': datetime.now().isoformat()
        }
        
        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
            },
            'body': json.dumps(response_data)
        }
        
    except Exception as e:
        logger.error(f"Error processing request: {str(e)}")
        return {
            'statusCode': 500,
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*'
            },
            'body': json.dumps({
                'error': 'Internal server error'
            })
        }

Lambda Environment Variables

javascript
// Using environment variables
const DB_HOST = process.env.DB_HOST;
const DB_PASSWORD = process.env.DB_PASSWORD;
const API_KEY = process.env.API_KEY;

exports.handler = async (event) => {
    const dbConnection = await connectToDatabase({
        host: DB_HOST,
        password: DB_PASSWORD
    });
    
    // Function logic here
};

Lambda with AWS SDK

javascript
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
const s3 = new AWS.S3();

exports.handler = async (event) => {
    try {
        // DynamoDB operation
        const params = {
            TableName: 'users',
            Item: {
                id: event.userId,
                name: event.name,
                timestamp: Date.now()
            }
        };
        
        await dynamodb.put(params).promise();
        
        // S3 operation
        const s3Params = {
            Bucket: 'my-bucket',
            Key: `users/${event.userId}.json`,
            Body: JSON.stringify(params.Item),
            ContentType: 'application/json'
        };
        
        await s3.upload(s3Params).promise();
        
        return {
            statusCode: 200,
            body: JSON.stringify({ message: 'User created successfully' })
        };
        
    } catch (error) {
        console.error('Error:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({ error: 'Failed to create user' })
        };
    }
};

Azure Functions

Azure Functions is Microsoft's serverless compute service.

HTTP Trigger Function

javascript
// Azure Function (Node.js)
module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    const name = req.query.name || (req.body && req.body.name);
    const age = req.query.age || (req.body && req.body.age);

    if (!name || !age) {
        context.res = {
            status: 400,
            headers: {
                'Content-Type': 'application/json'
            },
            body: {
                error: 'Name and age parameters are required'
            }
        };
        return;
    }

    const responseMessage = {
        message: `Hello, ${name}! You are ${age} years old.`,
        timestamp: new Date().toISOString(),
        invocationId: context.invocationId
    };

    context.res = {
        status: 200,
        headers: {
            'Content-Type': 'application/json'
        },
        body: responseMessage
    };
};

Timer Trigger Function

javascript
// Timer trigger function that runs every 5 minutes
module.exports = async function (context, myTimer) {
    const timeStamp = new Date().toISOString();
    
    if (myTimer.isPastDue) {
        context.log('Timer function is running late!');
    }
    
    context.log('Timer trigger function ran!', timeStamp);
    
    // Perform scheduled task
    try {
        await performScheduledTask();
        context.log('Scheduled task completed successfully');
    } catch (error) {
        context.log.error('Scheduled task failed:', error);
    }
};

async function performScheduledTask() {
    // Implement your scheduled logic here
    // e.g., data cleanup, report generation, etc.
}

Blob Trigger Function

javascript
// Triggered when a blob is added to storage
module.exports = async function (context, myBlob) {
    context.log('Blob trigger function processing blob:', context.bindingData.name);
    context.log('Blob size:', myBlob.length, 'bytes');
    
    try {
        // Process the blob
        const processedData = await processBlob(myBlob);
        
        // Save processed data
        context.bindings.outputBlob = processedData;
        
        context.log('Blob processed successfully');
    } catch (error) {
        context.log.error('Error processing blob:', error);
        throw error;
    }
};

async function processBlob(blobData) {
    // Implement blob processing logic
    // e.g., image resizing, data transformation, etc.
    return blobData;
}

Google Cloud Functions

Google Cloud Functions is Google's serverless execution environment.

HTTP Function

javascript
// Google Cloud Function (Node.js)
const functions = require('@google-cloud/functions-framework');

functions.http('helloWorld', (req, res) => {
    // Set CORS headers
    res.set('Access-Control-Allow-Origin', '*');
    res.set('Access-Control-Allow-Methods', 'GET, POST');
    res.set('Access-Control-Allow-Headers', 'Content-Type');

    if (req.method === 'OPTIONS') {
        res.status(204).send('');
        return;
    }

    const { name, age } = req.body;

    if (!name || !age) {
        res.status(400).json({
            error: 'Name and age are required'
        });
        return;
    }

    const response = {
        message: `Hello ${name}, you are ${age} years old!`,
        timestamp: new Date().toISOString(),
        function: 'helloWorld'
    };

    res.status(200).json(response);
});

Cloud Storage Trigger

javascript
const functions = require('@google-cloud/functions-framework');

functions.cloudEvent('processFile', (cloudEvent) => {
    console.log('Cloud Storage event:', cloudEvent);
    
    const file = cloudEvent.data;
    console.log(`File: ${file.name}`);
    console.log(`Bucket: ${file.bucket}`);
    console.log(`Event Type: ${cloudEvent.type}`);
    
    // Process the file
    if (file.name.endsWith('.jpg') || file.name.endsWith('.png')) {
        return processImage(file);
    } else if (file.name.endsWith('.csv')) {
        return processCSV(file);
    }
    
    console.log('File type not supported for processing');
});

async function processImage(file) {
    console.log(`Processing image: ${file.name}`);
    // Implement image processing logic
}

async function processCSV(file) {
    console.log(`Processing CSV: ${file.name}`);
    // Implement CSV processing logic
}

Pub/Sub Trigger

javascript
const functions = require('@google-cloud/functions-framework');

functions.cloudEvent('processPubSubMessage', (cloudEvent) => {
    const message = cloudEvent.data.message;
    const data = Buffer.from(message.data, 'base64').toString();
    
    console.log('Received message:', data);
    
    try {
        const parsedData = JSON.parse(data);
        return processMessage(parsedData);
    } catch (error) {
        console.error('Error parsing message:', error);
    }
});

async function processMessage(data) {
    console.log('Processing message data:', data);
    // Implement message processing logic
}

Serverless Framework

The Serverless Framework is an open-source tool for building and deploying serverless applications.

serverless.yml Configuration

yaml
service: my-serverless-app
frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1
  stage: ${opt:stage, 'dev'}
  environment:
    STAGE: ${self:provider.stage}
    DB_HOST: ${env:DB_HOST}
    API_KEY: ${env:API_KEY}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource:
        - "arn:aws:dynamodb:${self:provider.region}:*:table/users"
        - "arn:aws:dynamodb:${self:provider.region}:*:table/users/index/*"

functions:
  hello:
    handler: src/handlers/hello.handler
    events:
      - http:
          path: hello
          method: get
          cors: true
  
  createUser:
    handler: src/handlers/users.create
    events:
      - http:
          path: users
          method: post
          cors: true
          
  getUser:
    handler: src/handlers/users.get
    events:
      - http:
          path: users/{id}
          method: get
          cors: true
          
  processFile:
    handler: src/handlers/files.process
    events:
      - s3:
          bucket: my-upload-bucket
          event: s3:ObjectCreated:*
          rules:
            - suffix: .jpg
            - suffix: .png
            
  scheduledTask:
    handler: src/handlers/scheduled.task
    events:
      - schedule: rate(5 minutes)

resources:
  Resources:
    UsersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: users
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

plugins:
  - serverless-offline
  - serverless-webpack

custom:
  webpack:
    webpackConfig: 'webpack.config.js'
    includeModules: true

Multi-Environment Configuration

yaml
# serverless.yml
provider:
  name: aws
  runtime: nodejs18.x
  stage: ${opt:stage, 'dev'}
  region: ${opt:region, 'us-east-1'}
  environment:
    STAGE: ${self:provider.stage}
    DB_TABLE: ${self:custom.tableName}
    API_URL: ${self:custom.apiUrl.${self:provider.stage}}

custom:
  tableName: users-${self:provider.stage}
  apiUrl:
    dev: https://dev-api.example.com
    staging: https://staging-api.example.com
    prod: https://api.example.com
  
  # Stage-specific configurations
  dev:
    logLevel: DEBUG
    timeout: 30
  staging:
    logLevel: INFO
    timeout: 10
  prod:
    logLevel: WARN
    timeout: 5

functions:
  api:
    handler: src/api.handler
    timeout: ${self:custom.${self:provider.stage}.timeout}
    environment:
      LOG_LEVEL: ${self:custom.${self:provider.stage}.logLevel}

Deployment Strategies

AWS SAM (Serverless Application Model)

yaml
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: My Serverless Application

Globals:
  Function:
    Timeout: 30
    Runtime: nodejs18.x
    Environment:
      Variables:
        STAGE: !Ref Environment

Parameters:
  Environment:
    Type: String
    Default: dev
    AllowedValues:
      - dev
      - staging
      - prod

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: hello.handler
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
            
  UsersFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: src/
      Handler: users.handler
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref UsersTable
      Events:
        CreateUser:
          Type: Api
          Properties:
            Path: /users
            Method: post
        GetUser:
          Type: Api
          Properties:
            Path: /users/{id}
            Method: get
            
  UsersTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      TableName: !Sub users-${Environment}
      PrimaryKey:
        Name: id
        Type: String

Outputs:
  HelloWorldApi:
    Description: "API Gateway endpoint URL"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"

Deployment Commands

bash
# Serverless Framework
serverless deploy --stage prod
serverless deploy function --function hello --stage prod
serverless logs --function hello --stage prod

# AWS SAM
sam build
sam deploy --guided
sam deploy --parameter-overrides Environment=prod

# Google Cloud Functions
gcloud functions deploy helloWorld \
    --runtime nodejs18 \
    --trigger-http \
    --allow-unauthenticated

# Azure Functions
func azure functionapp publish my-function-app

Error Handling and Monitoring

Error Handling Best Practices

javascript
// Comprehensive error handling
exports.handler = async (event, context) => {
    try {
        // Validate input
        const validationError = validateInput(event);
        if (validationError) {
            return createErrorResponse(400, validationError);
        }
        
        // Process request
        const result = await processRequest(event);
        
        return createSuccessResponse(result);
        
    } catch (error) {
        console.error('Function error:', error);
        
        // Handle specific error types
        if (error.code === 'ValidationException') {
            return createErrorResponse(400, 'Invalid input data');
        } else if (error.code === 'ResourceNotFoundException') {
            return createErrorResponse(404, 'Resource not found');
        } else if (error.code === 'ThrottlingException') {
            return createErrorResponse(429, 'Too many requests');
        }
        
        // Generic error response
        return createErrorResponse(500, 'Internal server error');
    }
};

function validateInput(event) {
    // Implement validation logic
    const body = JSON.parse(event.body);
    if (!body.name) return 'Name is required';
    if (!body.email) return 'Email is required';
    return null;
}

function createSuccessResponse(data) {
    return {
        statusCode: 200,
        headers: {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        body: JSON.stringify(data)
    };
}

function createErrorResponse(statusCode, message) {
    return {
        statusCode,
        headers: {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
        body: JSON.stringify({ error: message })
    };
}

Monitoring with CloudWatch

javascript
const AWS = require('aws-sdk');
const cloudwatch = new AWS.CloudWatch();

// Custom metrics
async function putCustomMetric(metricName, value, unit = 'Count') {
    const params = {
        Namespace: 'MyApp/Functions',
        MetricData: [{
            MetricName: metricName,
            Value: value,
            Unit: unit,
            Timestamp: new Date()
        }]
    };
    
    await cloudwatch.putMetricData(params).promise();
}

exports.handler = async (event) => {
    const startTime = Date.now();
    
    try {
        // Your function logic here
        const result = await processRequest(event);
        
        // Record success metric
        await putCustomMetric('ProcessSuccess', 1);
        
        // Record execution time
        const executionTime = Date.now() - startTime;
        await putCustomMetric('ExecutionTime', executionTime, 'Milliseconds');
        
        return result;
        
    } catch (error) {
        // Record error metric
        await putCustomMetric('ProcessError', 1);
        throw error;
    }
};

Cold Start Optimization

Strategies to Reduce Cold Starts

javascript
// Keep connections outside the handler
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();

// Connection pooling
const connectionPool = new Map();

function getConnection(config) {
    const key = JSON.stringify(config);
    if (!connectionPool.has(key)) {
        connectionPool.set(key, createConnection(config));
    }
    return connectionPool.get(key);
}

// Provisioned concurrency function
exports.handler = async (event) => {
    // Handler logic using pre-initialized connections
    const connection = getConnection({
        host: process.env.DB_HOST,
        port: process.env.DB_PORT
    });
    
    // Process request
    return await processWithConnection(connection, event);
};

// Warm-up function
exports.warmUp = async (event) => {
    if (event.source === 'serverless-plugin-warmup') {
        console.log('WarmUp - Lambda is warm!');
        return 'Lambda is warm!';
    }
    
    // Normal function execution
    return await exports.handler(event);
};

Memory and Timeout Optimization

yaml
# serverless.yml
functions:
  lowMemoryFunction:
    handler: handlers/simple.handler
    memorySize: 128
    timeout: 5
    
  highMemoryFunction:
    handler: handlers/complex.handler
    memorySize: 1024
    timeout: 30
    
  provisionedFunction:
    handler: handlers/critical.handler
    memorySize: 512
    timeout: 15
    provisionedConcurrency: 5  # Keep 5 instances warm

Best Practices

1. Function Design

javascript
// Single responsibility
exports.createUser = async (event) => {
    // Only handles user creation
};

exports.getUser = async (event) => {
    // Only handles user retrieval
};

// Keep functions small and focused
exports.handler = async (event) => {
    // Validate input
    // Process request
    // Return response
    // Each step should be simple and clear
};

2. Environment Configuration

javascript
// Use environment variables for configuration
const config = {
    dbHost: process.env.DB_HOST,
    dbPort: process.env.DB_PORT || 5432,
    apiKey: process.env.API_KEY,
    environment: process.env.STAGE || 'dev'
};

// Validate required environment variables
const requiredEnvVars = ['DB_HOST', 'API_KEY'];
for (const envVar of requiredEnvVars) {
    if (!process.env[envVar]) {
        throw new Error(`Required environment variable ${envVar} is not set`);
    }
}

3. Security

javascript
// Input validation
function validateRequest(event) {
    const body = JSON.parse(event.body);
    
    // Sanitize input
    const sanitizedData = {
        name: sanitizeString(body.name),
        email: sanitizeEmail(body.email)
    };
    
    // Validate format
    if (!isValidEmail(sanitizedData.email)) {
        throw new Error('Invalid email format');
    }
    
    return sanitizedData;
}

// Use IAM roles with minimal permissions
// serverless.yml
provider:
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:GetItem
        - dynamodb:PutItem
      Resource: "arn:aws:dynamodb:region:account:table/specific-table"

This comprehensive guide covers serverless computing fundamentals, major cloud providers, deployment strategies, monitoring, and best practices for building scalable serverless applications.

Created by Eren Demir.