SERVERLESS ARCHITECTURE

Serverless API with Lambda

RESTful API for task management using AWS Lambda, API Gateway, and DynamoDB with complete CRUD operations.

LambdaAPI GatewayDynamoDBPythonServerlessREST API

PROJECT OVERVIEW

OBJECTIVE

Build a fully serverless REST API with automatic scaling, pay-per-use pricing, and zero server management overhead.

APPROACH

Leveraged AWS Lambda for compute, API Gateway for HTTP routing, and DynamoDB for NoSQL data storage with automated deployment scripts.

OUTCOME

Production-ready serverless API with CRUD operations, automatic scaling, and comprehensive error handling.

ARCHITECTURE DIAGRAM

Serverless API Architecture

API ENDPOINTS

  • GET/tasks - List all tasks
  • POST/tasks - Create new task
  • GET/tasks/:id - Get task by ID
  • PUT/tasks/:id - Update task
  • DELETE/tasks/:id - Delete task

KEY FEATURES

  • Automatic scaling with zero configuration
  • Pay-per-request pricing model
  • CORS support for web applications
  • CloudWatch logging and monitoring

LAMBDA FUNCTION CODE

lambda_function.py
import json
import boto3
from decimal import Decimal
from datetime import datetime

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Tasks')

class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            return float(obj)
        return super(DecimalEncoder, self).default(obj)

def lambda_handler(event, context):
    """Main handler for API Gateway requests"""
    http_method = event['httpMethod']
    path = event['path']
    
    try:
        if http_method == 'GET' and path == '/tasks':
            return list_tasks()
        elif http_method == 'POST' and path == '/tasks':
            return create_task(json.loads(event['body']))
        elif http_method == 'GET' and '/tasks/' in path:
            task_id = path.split('/')[-1]
            return get_task(task_id)
        elif http_method == 'PUT' and '/tasks/' in path:
            task_id = path.split('/')[-1]
            return update_task(task_id, json.loads(event['body']))
        elif http_method == 'DELETE' and '/tasks/' in path:
            task_id = path.split('/')[-1]
            return delete_task(task_id)
        else:
            return response(404, {'error': 'Not found'})
    except Exception as e:
        return response(500, {'error': str(e)})

def list_tasks():
    """Get all tasks"""
    result = table.scan()
    return response(200, {
        'tasks': result['Items'],
        'count': len(result['Items'])
    })

def create_task(body):
    """Create a new task"""
    task_id = str(int(datetime.now().timestamp() * 1000))
    
    item = {
        'id': task_id,
        'title': body.get('title'),
        'description': body.get('description', ''),
        'status': body.get('status', 'pending'),
        'created_at': datetime.now().isoformat(),
        'updated_at': datetime.now().isoformat()
    }
    
    table.put_item(Item=item)
    return response(201, {'task': item})

def get_task(task_id):
    """Get a specific task by ID"""
    result = table.get_item(Key={'id': task_id})
    
    if 'Item' not in result:
        return response(404, {'error': 'Task not found'})
    
    return response(200, {'task': result['Item']})

def update_task(task_id, body):
    """Update an existing task"""
    result = table.get_item(Key={'id': task_id})
    
    if 'Item' not in result:
        return response(404, {'error': 'Task not found'})
    
    update_expr = 'SET '
    expr_values = {}
    
    if 'title' in body:
        update_expr += 'title = :title, '
        expr_values[':title'] = body['title']
    
    if 'description' in body:
        update_expr += 'description = :desc, '
        expr_values[':desc'] = body['description']
    
    if 'status' in body:
        update_expr += '#status = :status, '
        expr_values[':status'] = body['status']
    
    update_expr += 'updated_at = :updated'
    expr_values[':updated'] = datetime.now().isoformat()
    
    table.update_item(
        Key={'id': task_id},
        UpdateExpression=update_expr,
        ExpressionAttributeNames={'#status': 'status'},
        ExpressionAttributeValues=expr_values
    )
    
    updated = table.get_item(Key={'id': task_id})
    return response(200, {'task': updated['Item']})

def delete_task(task_id):
    """Delete a task"""
    result = table.get_item(Key={'id': task_id})
    
    if 'Item' not in result:
        return response(404, {'error': 'Task not found'})
    
    table.delete_item(Key={'id': task_id})
    return response(200, {'message': 'Task deleted successfully'})

def response(status_code, body):
    """Format API Gateway response"""
    return {
        'statusCode': status_code,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE',
            'Access-Control-Allow-Headers': 'Content-Type'
        },
        'body': json.dumps(body, cls=DecimalEncoder)
    }

API USAGE EXAMPLES

CREATE TASK

curl -X POST https://api.example.com/tasks \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Deploy Lambda Function",
    "description": "Deploy serverless API to production",
    "status": "in-progress"
  }'

LIST ALL TASKS

curl -X GET https://api.example.com/tasks

UPDATE TASK

curl -X PUT https://api.example.com/tasks/1234567890 \
  -H "Content-Type: application/json" \
  -d '{"status": "completed"}'

KEY LEARNINGS

TECHNICAL SKILLS

  • • Serverless architecture design patterns
  • • AWS Lambda function development
  • • API Gateway configuration and routing
  • • DynamoDB data modeling and operations
  • • RESTful API design principles

BEST PRACTICES

  • • Use IAM roles for Lambda permissions
  • • Implement proper error handling and logging
  • • Enable CORS for web application access
  • • Set appropriate Lambda timeout and memory
  • • Use environment variables for configuration