Cloud Computing with AWS: A Comprehensive Guide


Cloud Computing with AWS: A Comprehensive Guide

Amazon Web Services (AWS) is the leading cloud platform, offering a vast array of services for building modern applications. This guide will help you master AWS and build scalable cloud solutions.

Getting Started with AWS

First, let's set up our AWS environment:

  1. Create an AWS account
  2. Install AWS CLI
  3. Configure credentials
# Install AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# Configure AWS credentials
aws configure

Core AWS Services

1. Amazon EC2 (Elastic Compute Cloud)

Launch and manage virtual servers:

# Launch an EC2 instance
aws ec2 run-instances \
    --image-id ami-0c55b159cbfafe1f0 \
    --instance-type t2.micro \
    --key-name my-key-pair \
    --security-group-ids sg-903004f8 \
    --subnet-id subnet-6e7f829e

# List instances
aws ec2 describe-instances

# Stop instance
aws ec2 stop-instances --instance-ids i-1234567890abcdef0

# Terminate instance
aws ec2 terminate-instances --instance-ids i-1234567890abcdef0

2. Amazon S3 (Simple Storage Service)

Object storage for any type of data:

# Create bucket
aws s3 mb s3://my-bucket-name

# Upload file
aws s3 cp file.txt s3://my-bucket-name/

# List objects
aws s3 ls s3://my-bucket-name/

# Download file
aws s3 cp s3://my-bucket-name/file.txt ./

# Delete object
aws s3 rm s3://my-bucket-name/file.txt

# Delete bucket
aws s3 rb s3://my-bucket-name --force

3. Amazon RDS (Relational Database Service)

Managed relational databases:

# Create database instance
aws rds create-db-instance \
    --db-instance-identifier mydb \
    --db-instance-class db.t3.micro \
    --engine postgres \
    --master-username admin \
    --master-user-password mypassword \
    --allocated-storage 20

# List instances
aws rds describe-db-instances

# Delete instance
aws rds delete-db-instance \
    --db-instance-identifier mydb \
    --skip-final-snapshot

Project: Serverless Web Application

Let's build a complete serverless application using AWS services:

1. API Gateway Configuration

# serverless.yml
service: serverless-api

provider:
  name: aws
  runtime: nodejs18.x
  stage: prod
  region: us-east-1
  
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/${self:custom.tableName}"

custom:
  tableName: users-table

functions:
  createUser:
    handler: src/handlers/createUser.handler
    events:
      - http:
          path: users
          method: post
          cors: true
  
  getUser:
    handler: src/handlers/getUser.handler
    events:
      - http:
          path: users/{id}
          method: get
          cors: true
  
  updateUser:
    handler: src/handlers/updateUser.handler
    events:
      - http:
          path: users/{id}
          method: put
          cors: true
  
  deleteUser:
    handler: src/handlers/deleteUser.handler
    events:
      - http:
          path: users/{id}
          method: delete
          cors: true

resources:
  Resources:
    UsersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:custom.tableName}
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

2. Lambda Functions

// src/handlers/createUser.ts
import { DynamoDB } from 'aws-sdk'
import { v4 as uuidv4 } from 'uuid'
import { APIGatewayProxyHandler } from 'aws-lambda'

const dynamodb = new DynamoDB.DocumentClient()
const tableName = process.env.USERS_TABLE!

export const handler: APIGatewayProxyHandler = async (event) => {
  try {
    const data = JSON.parse(event.body || '{}')
    const timestamp = new Date().getTime()
    
    const user = {
      id: uuidv4(),
      name: data.name,
      email: data.email,
      createdAt: timestamp,
      updatedAt: timestamp,
    }
    
    await dynamodb.put({
      TableName: tableName,
      Item: user,
    }).promise()
    
    return {
      statusCode: 201,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify(user),
    }
  } catch (error) {
    console.error(error)
    
    return {
      statusCode: 500,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify({ error: 'Could not create user' }),
    }
  }
}

// src/handlers/getUser.ts
export const handler: APIGatewayProxyHandler = async (event) => {
  try {
    const { id } = event.pathParameters || {}
    
    const result = await dynamodb.get({
      TableName: tableName,
      Key: { id },
    }).promise()
    
    if (!result.Item) {
      return {
        statusCode: 404,
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
        body: JSON.stringify({ error: 'User not found' }),
      }
    }
    
    return {
      statusCode: 200,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify(result.Item),
    }
  } catch (error) {
    console.error(error)
    
    return {
      statusCode: 500,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify({ error: 'Could not get user' }),
    }
  }
}

// src/handlers/updateUser.ts
export const handler: APIGatewayProxyHandler = async (event) => {
  try {
    const { id } = event.pathParameters || {}
    const data = JSON.parse(event.body || '{}')
    const timestamp = new Date().getTime()
    
    const params = {
      TableName: tableName,
      Key: { id },
      ExpressionAttributeNames: {
        '#name': 'name',
        '#email': 'email',
        '#updatedAt': 'updatedAt',
      },
      ExpressionAttributeValues: {
        ':name': data.name,
        ':email': data.email,
        ':updatedAt': timestamp,
      },
      UpdateExpression: 'SET #name = :name, #email = :email, #updatedAt = :updatedAt',
      ReturnValues: 'ALL_NEW',
    }
    
    const result = await dynamodb.update(params).promise()
    
    return {
      statusCode: 200,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify(result.Attributes),
    }
  } catch (error) {
    console.error(error)
    
    return {
      statusCode: 500,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify({ error: 'Could not update user' }),
    }
  }
}

// src/handlers/deleteUser.ts
export const handler: APIGatewayProxyHandler = async (event) => {
  try {
    const { id } = event.pathParameters || {}
    
    await dynamodb.delete({
      TableName: tableName,
      Key: { id },
    }).promise()
    
    return {
      statusCode: 204,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      body: '',
    }
  } catch (error) {
    console.error(error)
    
    return {
      statusCode: 500,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      body: JSON.stringify({ error: 'Could not delete user' }),
    }
  }
}

3. Frontend Application

// src/api/users.ts
const API_URL = process.env.NEXT_PUBLIC_API_URL

export async function createUser(data: any) {
  const response = await fetch(`${API_URL}/users`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(data),
  })
  
  if (!response.ok) {
    throw new Error('Failed to create user')
  }
  
  return response.json()
}

export async function getUser(id: string) {
  const response = await fetch(`${API_URL}/users/${id}`)
  
  if (!response.ok) {
    throw new Error('Failed to get user')
  }
  
  return response.json()
}

export async function updateUser(id: string, data: any) {
  const response = await fetch(`${API_URL}/users/${id}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(data),
  })
  
  if (!response.ok) {
    throw new Error('Failed to update user')
  }
  
  return response.json()
}

export async function deleteUser(id: string) {
  const response = await fetch(`${API_URL}/users/${id}`, {
    method: 'DELETE',
  })
  
  if (!response.ok) {
    throw new Error('Failed to delete user')
  }
}

// src/components/UserForm.tsx
import { useState } from 'react'
import { createUser, updateUser } from '../api/users'

interface UserFormProps {
  user?: any
  onSubmit: () => void
}

export function UserForm({ user, onSubmit }: UserFormProps) {
  const [name, setName] = useState(user?.name || '')
  const [email, setEmail] = useState(user?.email || '')
  const [loading, setLoading] = useState(false)
  
  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    setLoading(true)
    
    try {
      if (user) {
        await updateUser(user.id, { name, email })
      } else {
        await createUser({ name, email })
      }
      
      onSubmit()
    } catch (error) {
      console.error(error)
      alert('Failed to save user')
    } finally {
      setLoading(false)
    }
  }
  
  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div>
        <label className="block text-sm font-medium">
          Name
        </label>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
          className="mt-1 block w-full rounded-md border-gray-300"
          required
        />
      </div>
      
      <div>
        <label className="block text-sm font-medium">
          Email
        </label>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          className="mt-1 block w-full rounded-md border-gray-300"
          required
        />
      </div>
      
      <button
        type="submit"
        disabled={loading}
        className="bg-blue-500 text-white px-4 py-2 rounded-md"
      >
        {loading ? 'Saving...' : 'Save'}
      </button>
    </form>
  )
}

Infrastructure as Code with AWS CDK

Define infrastructure using TypeScript:

// lib/app-stack.ts
import * as cdk from 'aws-cdk-lib'
import * as s3 from 'aws-cdk-lib/aws-s3'
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'
import * as route53 from 'aws-cdk-lib/aws-route53'
import * as targets from 'aws-cdk-lib/aws-route53-targets'
import * as acm from 'aws-cdk-lib/aws-certificatemanager'

export class AppStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props)
    
    // S3 bucket for website hosting
    const websiteBucket = new s3.Bucket(this, 'WebsiteBucket', {
      websiteIndexDocument: 'index.html',
      websiteErrorDocument: 'error.html',
      publicReadAccess: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    })
    
    // CloudFront distribution
    const distribution = new cloudfront.Distribution(this, 'Distribution', {
      defaultBehavior: {
        origin: new origins.S3Origin(websiteBucket),
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
        cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD,
      },
      priceClass: cloudfront.PriceClass.PRICE_CLASS_100,
    })
    
    // Output the distribution URL
    new cdk.CfnOutput(this, 'DistributionUrl', {
      value: distribution.distributionDomainName,
    })
  }
}

Best Practices

  1. Security

    • Use IAM roles and policies
    • Enable encryption at rest
    • Implement network security
    • Monitor and audit
  2. Cost Optimization

    • Use auto-scaling
    • Implement lifecycle policies
    • Monitor usage
    • Use spot instances
  3. High Availability

    • Deploy across regions
    • Use load balancers
    • Implement failover
    • Monitor health
  4. Performance

    • Use caching
    • Optimize database queries
    • Use CDN
    • Monitor metrics

Common AWS Patterns

  1. Serverless Web Application
import * as lambda from 'aws-cdk-lib/aws-lambda'
import * as apigateway from 'aws-cdk-lib/aws-apigateway'

// Create Lambda function
const handler = new lambda.Function(this, 'Handler', {
  runtime: lambda.Runtime.NODEJS_18_X,
  code: lambda.Code.fromAsset('lambda'),
  handler: 'index.handler',
})

// Create API Gateway
const api = new apigateway.RestApi(this, 'Api', {
  restApiName: 'My API',
})

api.root.addMethod('GET', new apigateway.LambdaIntegration(handler))
  1. Event-Driven Architecture
import * as sqs from 'aws-cdk-lib/aws-sqs'
import * as sns from 'aws-cdk-lib/aws-sns'
import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'

// Create SQS queue
const queue = new sqs.Queue(this, 'Queue')

// Create SNS topic
const topic = new sns.Topic(this, 'Topic')

// Subscribe queue to topic
topic.addSubscription(new subscriptions.SqsSubscription(queue))

Conclusion

AWS provides powerful services for building modern applications:

  • Scalable infrastructure
  • Managed services
  • Global reach
  • Pay-as-you-go pricing

Keep exploring AWS services and best practices to build robust cloud applications.


Further Reading