Skip to main content
Back to Blog
AWSServerlessLambdaCloud ArchitectureCost Optimization

Building a Serverless Application on AWS: When It Makes Sense

DEVOIDA Team
5 min read

Serverless isn't always the answer—here's how to know when it is

When Serverless Makes Sense

scenarioserverlesstraditional
Variable/unpredictable trafficExcellentWasteful
Consistent high trafficExpensiveCost-effective
Quick MVP/prototypeExcellentSlower
Long-running processesLimited (15 min max)Better
Event-driven workloadsExcellentOverkill
Real-time websocketsPossible but complexSimpler

AWS Serverless Architecture

Typical Serverless Stack:

Compute:
  - AWS Lambda: Function execution
  - Step Functions: Workflow orchestration
  
API:
  - API Gateway: REST/HTTP APIs
  - AppSync: GraphQL APIs
  
Data:
  - DynamoDB: NoSQL database
  - Aurora Serverless: Relational database
  - S3: Object storage
  
Events:
  - EventBridge: Event routing
  - SQS: Message queuing
  - SNS: Pub/sub messaging
  
Auth:
  - Cognito: User authentication

Lambda Function Example

// Example: API endpoint with Lambda
import { APIGatewayProxyHandler } from 'aws-lambda';
import { DynamoDB } from 'aws-sdk';

const dynamodb = new DynamoDB.DocumentClient();

export const handler: APIGatewayProxyHandler = async (event) => {
  try {
    const { id } = event.pathParameters || {};
    
    if (!id) {
      return {
        statusCode: 400,
        body: JSON.stringify({ error: 'ID required' })
      };
    }

    const result = await dynamodb.get({
      TableName: process.env.TABLE_NAME!,
      Key: { id }
    }).promise();

    if (!result.Item) {
      return {
        statusCode: 404,
        body: JSON.stringify({ error: 'Not found' })
      };
    }

    return {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify(result.Item)
    };
  } catch (error) {
    console.error('Error:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Internal server error' })
    };
  }
};

Cost Analysis

Serverless Pricing Model

// Lambda pricing breakdown
const lambdaPricing = {
  requests: 0.20,        // per 1M requests
  compute: 0.0000166667, // per GB-second
  
  // Example: 1M requests/month, 500ms avg, 512MB memory
  example: {
    requests: 1_000_000,
    avgDurationMs: 500,
    memoryMB: 512,
    
    // Calculate
    requestCost: 1 * 0.20, // $0.20
    computeGBSeconds: 1_000_000 * 0.5 * 0.5, // 250,000 GB-seconds
    computeCost: 250_000 * 0.0000166667, // $4.17
    
    totalMonthly: 4.37 // $4.37/month
  }
};
Assumes 500ms avg duration, 512MB memory for Lambda
trafficserverlessCostec2tMicrowinnerec2tSmallec2tMedium
100K requests/month$0.50$8.50Serverless
1M requests/month$4.50$8.50Serverless
10M requests/month$45EC2$17
100M requests/month$450EC2$34

Key Insight: Serverless is cost-effective for variable/low traffic. Once you exceed ~5-10M consistent requests/month, traditional servers often become cheaper.

Common Serverless Patterns

Pattern 1: API Backend

Architecture:
  Client -> API Gateway -> Lambda -> DynamoDB
  
Use Case: REST API for web/mobile apps
Pros: Auto-scaling, pay-per-use, zero servers
Cons: Cold starts, 29-second timeout (API Gateway)

Pattern 2: Event Processing

Architecture:
  S3 Upload -> Lambda -> Process -> Store Result
  
Use Case: Image processing, file conversion
Pros: Automatic triggers, parallel processing
Cons: 15-minute timeout, temp storage limits

Pattern 3: Scheduled Tasks

Architecture:
  EventBridge (cron) -> Lambda -> External APIs
  
Use Case: Data sync, reports, cleanup tasks
Pros: No server maintenance, precise scheduling
Cons: 15-minute limit per execution

Serverless Best Practices

// Best Practice 1: Minimize cold starts
// Initialize outside handler
const dynamodb = new DynamoDB.DocumentClient(); // ✅ Outside
let cachedConfig: Config | null = null;

export const handler = async (event) => {
  // Reuse connections and cached data
  if (!cachedConfig) {
    cachedConfig = await loadConfig();
  }
  // Handler logic...
};

// Best Practice 2: Use appropriate memory
// More memory = more CPU = faster execution
// Sometimes 1024MB is cheaper than 512MB (runs faster)

// Best Practice 3: Keep functions focused
// One function per responsibility
// ❌ handleEverything()
// ✅ createUser(), updateUser(), deleteUser()

When NOT to Use Serverless

Avoid Serverless For:

  • • Long-running processes (over 15 minutes)
  • • Consistent high-traffic applications
  • • Applications requiring persistent connections
  • • Workloads with predictable, steady load
  • • Applications with strict latency requirements

Migration Path

## Gradual Migration to Serverless

Phase 1: New Features
- Build new features as Lambda functions
- Keep existing infrastructure
- Learn and iterate

Phase 2: Extract Services
- Identify stateless components
- Migrate one service at a time
- Monitor costs and performance

Phase 3: Full Migration (if beneficial)
- Migrate remaining components
- Decommission servers
- Optimize for serverless patterns

Need help designing your serverless architecture?

We help companies build cost-effective, scalable serverless solutions on AWS.

Get Architecture Advice