Writing Lambda function

We will create a Lambda function that writes data to the DynamoDB table and uploads images to S3.

Edit SAM Template

This function requires two additional S3 buckets:

  • BookImageShop - stores the original uploaded book images
  • BookImageResizeShop - stores resized images for serving to the frontend, with a public bucket policy and CORS configuration
  1. Open the template.yaml file in the fcaj-book-shop folder.

  2. Add the following parameters under the Parameters section:

    bookImageShopBucketName:
      Type: String
      Default: book-image-shop-by-tranvix
    
    bookImageResizeShopBucketName:
      Type: String
      Default: book-image-resize-shop-by-tranvix
    

    Add Parameters

  3. Add the S3 bucket resources under the Resources section:

    BookImageShop:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: !Ref bookImageShopBucketName
        PublicAccessBlockConfiguration:
          BlockPublicAcls: false
          BlockPublicPolicy: false
          IgnorePublicAcls: false
          RestrictPublicBuckets: false
    
    BookImageResizeShop:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: !Ref bookImageResizeShopBucketName
        PublicAccessBlockConfiguration:
          BlockPublicAcls: false
          BlockPublicPolicy: false
          IgnorePublicAcls: false
          RestrictPublicBuckets: false
        CorsConfiguration:
          CorsRules:
            - AllowedHeaders:
                - "*"
              AllowedMethods:
                - GET
                - PUT
                - POST
                - DELETE
              AllowedOrigins:
                - "*"
    

    Add S3 Buckets

  4. Add the bucket policy for the resize bucket and the BookCreate Lambda function:

    BookImageResizeShopPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket: !Ref BookImageResizeShop
        PolicyDocument:
          Version: 2012-10-17
          Statement:
            - Action:
                - "s3:GetObject"
              Effect: Allow
              Principal: "*"
              Resource: !Join
                - ""
                - - "arn:aws:s3:::"
                  - !Ref BookImageResizeShop
                  - /*
    

    Add Bucket Policy and BookCreate function

  5. Add the BookCreate Lambda function resource:

    BookCreate:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: fcaj-book-shop/book_create
        Handler: book_create.lambda_handler
        Runtime: python3.13
        Environment:
          Variables:
            BUCKET_NAME: !Ref BookImageShop
            BUCKET_RESIZE_NAME: !Ref BookImageResizeShop
            TABLE_NAME: !Ref BooksTable
        FunctionName: book_create
        Architectures:
          - x86_64
        Policies:
          - Statement:
              - Sid: BookCreateItem
                Effect: Allow
                Action:
                  - dynamodb:PutItem
                  - s3:PutObject
                Resource:
                  - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${booksTableName}
                  - !Join
                    - ""
                    - - "arn:aws:s3:::"
                      - !Ref BookImageShop
                      - /*
    

    Add BookCreate resource

This resource defines a Lambda function named book_create that:

  • Uses Python 3.13 runtime on x86_64 architecture
  • Has environment variables for BUCKET_NAME, BUCKET_RESIZE_NAME, and TABLE_NAME
  • Is granted dynamodb:PutItem permission on the Books table
  • Is granted s3:PutObject permission on the BookImageShop bucket

Create Lambda Function Code

  1. Create the following directory structure:

    fcaj-book-shop
    ├── fcaj-book-shop
    │   ├── books_list
    │   │   └── books_list.py
    │   └── book_create
    │       ├── book_create.py
    │       └── requirements.txt
    └── template.yaml
    
    • Create the fcaj-book-shop/book_create folder inside the fcaj-book-shop directory.
    • Create the book_create.py file and copy the following code into it:
    import base64
    from multipart import MultipartParser
    from io import BytesIO
    import boto3
    import os
    
    BUCKET = os.environ['BUCKET_NAME']
    BUCKET_RESIZE = os.environ['BUCKET_RESIZE_NAME']
    TABLE = os.environ['TABLE_NAME']
    
    s3_client = boto3.client('s3')
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(TABLE)
    
    header_res = {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "OPTIONS,POST,GET,DELETE",
        "Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
    }
    
    def parse_multipart(body, boundary):
        parser = MultipartParser(stream=body, boundary=boundary)
    
        fields = {}
        files = {}
    
        for part in parser:
            if part.filename:
                files[part.name] = {
                    'file-name': part.filename,
                    'content': part.raw
                }
    
            else:
                fields[part.name] = part.value
    
        return fields, files
    
    
    def upload_to_s3(key, bucket, body):
        try:
            s3_client.put_object(Key=key, Bucket=bucket, Body=body)
        except Exception as e:
            print(f"Error uploading image to S3: {e}")
            raise Exception(f'Error uploading image to S3: {e}')
    
    
    def create_new_item(table, item):
        try:
            table.put_item(Item=item)
        except Exception as e:
            print(f"Error creating new item in DynamoDB table: {e}")
            raise Exception(f'Error creating new item in DynamoDB table: {e}')
    
    
    def lambda_handler(event, context):
        content_type = event['headers'].get(
            'Content-Type', '') or event['headers'].get('content-type', '')
    
        try:
            body = event['body']
    
            if event['isBase64Encoded']:
                body = BytesIO(base64.b64decode(body))
    
            boundary = content_type.split("boundary=")[1]
    
            fields, files = parse_multipart(body, boundary)
    
            # Upload image to s3
            file_name = files['image']['file-name']
            file_content = files['image']['content']
    
            upload_to_s3(file_name, BUCKET, file_content)
            s3_url = f'https://{BUCKET_RESIZE}.s3.amazonaws.com/{file_name}'
    
            # Create new item in DB
            item = {
                'id': fields.get('id', 0),
                'rv_id': 0,
                'name': fields.get('name', ''),
                'author': fields.get('author', ''),
                'price': fields.get('price', ''),
                'category': fields.get('category', ''),
                'description': fields.get('description', ''),
                'image': s3_url
            }
    
            create_new_item(table, item)
    
            return {
                "statusCode": 200,
                "headers": header_res,
                "body": "Successfully created item!",
            }
    
        except Exception as e:
            print(f"Error processing form data: {e}")
            raise Exception(f'Error processing form data: {e}')
    

    book_create.py source code

  2. Create the requirements.txt file in the book_create folder with:

    multipart
    

    requirements.txt

The multipart library is required for parsing multipart form data (file uploads) sent from the frontend. SAM will install this dependency during sam build.

Build and Deploy

  1. Run the following commands to build and validate the SAM template:

    sam validate
    sam build
    

    SAM Validate and Build

  2. Deploy the updated stack:

    sam deploy
    

    The changeset will show new resources being added:

    • BookCreateRole (AWS::IAM::Role)
    • BookCreate (AWS::Lambda::Function)
    • BookImageShop (AWS::S3::Bucket)
    • BookImageResizeShop (AWS::S3::Bucket)
    • BookImageResizeShopPolicy (AWS::S3::BucketPolicy)

    Enter y to confirm the deployment.

    SAM Deploy - Changeset

  3. Wait for the deployment to complete. You should see Successfully created/updated stack.

    Deploy Complete

Verify on AWS Console

  1. Open the Lambda console. You should now see both books_list and book_create functions.

    Lambda Functions

  2. Click on the book_create function. Review the Code source section.

    Code Source

  3. Review the Function overview section showing the function diagram.

    Function Overview

  4. Click the Configuration tab, then select Environment variables. Verify that the 3 environment variables are set correctly:

    • BUCKET_NAME = book-image-shop-by-tranvix
    • BUCKET_RESIZE_NAME = book-image-resize-shop-by-tranvix
    • TABLE_NAME = Books

    Environment Variables

  5. Select Permissions from the left menu. Click on the execution role name (e.g., fcaj-book-shop-BookCreateRole-...).

    Permissions - Execution Role

  6. At the IAM Role page, check the Permissions policies section. You should see:

    • AWSLambdaBasicExecutionRole (AWS managed) - for CloudWatch Logs
    • BookCreateRolePolicy0 (Customer inline) - the custom policy we defined

    IAM Role Permissions

  7. Click on BookCreateRolePolicy0 to expand it. Review the policy JSON that grants dynamodb:PutItem and s3:PutObject permissions.

    BookCreateRolePolicy0 JSON

Verify S3 Buckets

  1. Open the S3 console. Search for book-image. You should see both buckets created:

    • book-image-resize-shop-by-tranvix
    • book-image-shop-by-tranvix

    S3 Buckets

  2. Click on book-image-resize-shop-by-tranvix. Switch to the Permissions tab. Verify the Bucket policy allows public s3:GetObject access.

    Bucket Permissions

  3. Review the Bucket policy JSON.

    Bucket Policy JSON

  4. Scroll down to the Cross-origin resource sharing (CORS) section. Verify the CORS configuration allows all origins and methods (GET, PUT, POST, DELETE).

    CORS Configuration