AWS for Developers 2026 — EC2, S3, Lambda, RDS, and CloudFront Guide
Advertisement
Introduction
Why This Matters
AWS has 200+ services, but as a developer you need about 10. In 2026, the core developer toolkit is: S3 for files, Lambda for serverless functions, RDS for databases, CloudFront as a CDN, and CDK for infrastructure as code. This guide focuses on practical patterns you will use in every production project.
The Developer AWS Starter Kit
| Category | Service | Use Case |
|---|---|---|
| Compute | EC2, Lambda, ECS Fargate | Servers, serverless, containers |
| Storage | S3, EBS, EFS | Files, block storage, shared filesystem |
| Database | RDS, DynamoDB, ElastiCache | SQL, NoSQL, Redis |
| Network | CloudFront, Route 53, ALB | CDN, DNS, load balancing |
| Auth | Cognito, IAM | User pools, service permissions |
| Queue | SQS, SNS, EventBridge | Async messaging, events |
S3: File Storage for Everything
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const s3 = new S3Client({ region: process.env.AWS_REGION || 'us-east-1' })
const BUCKET = process.env.S3_BUCKET!
// Upload a file
export async function uploadFile(
key: string,
body: Buffer,
contentType: string
): Promise<string> {
await s3.send(new PutObjectCommand({
Bucket: BUCKET,
Key: key,
Body: body,
ContentType: contentType,
CacheControl: 'public, max-age=31536000',
}))
return `https://${BUCKET}.s3.amazonaws.com/${key}`
}
// Generate presigned URL for direct browser upload
export async function getUploadUrl(key: string, contentType: string): Promise<string> {
return getSignedUrl(
s3,
new PutObjectCommand({ Bucket: BUCKET, Key: key, ContentType: contentType }),
{ expiresIn: 3600 }
)
}
// Generate presigned URL for private file download
export async function getDownloadUrl(key: string, expiresIn = 3600): Promise<string> {
return getSignedUrl(
s3,
new GetObjectCommand({ Bucket: BUCKET, Key: key }),
{ expiresIn }
)
}Lambda: Serverless API Handler
// handler.ts — Typed Lambda HTTP handler
import type { APIGatewayProxyHandler } from 'aws-lambda'
export const handler: APIGatewayProxyHandler = async (event) => {
const method = event.httpMethod
const path = event.path
const headers = {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
}
try {
if (method === 'GET' && path === '/users') {
const users = await getUsers()
return { statusCode: 200, headers, body: JSON.stringify(users) }
}
if (method === 'POST' && path === '/users') {
const body = event.body ? JSON.parse(event.body) : {}
const user = await createUser(body)
return { statusCode: 201, headers, body: JSON.stringify(user) }
}
return { statusCode: 404, headers, body: JSON.stringify({ error: 'Not found' }) }
} catch (error) {
console.error('Handler error:', error)
return { statusCode: 500, headers, body: JSON.stringify({ error: 'Internal error' }) }
}
}# serverless.yml — Deploy with Serverless Framework
service: my-api
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
architecture: arm64
environment:
DATABASE_URL: ${ssm:/myapp/database-url}
functions:
api:
handler: dist/handler.handler
events:
- httpApi:
path: /{proxy+}
method: ANY
timeout: 30
memorySize: 512RDS: Managed PostgreSQL
import { Pool } from 'pg'
const pool = new Pool({
host: process.env.RDS_ENDPOINT,
port: 5432,
database: 'myapp',
user: process.env.RDS_USERNAME,
password: process.env.RDS_PASSWORD,
ssl: { require: true, rejectUnauthorized: false },
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
})Provision RDS with Terraform:
resource "aws_db_instance" "postgres" {
identifier = "myapp-db"
engine = "postgres"
engine_version = "16.2"
instance_class = "db.t3.medium"
allocated_storage = 20
storage_encrypted = true
db_name = "myapp"
username = "dbadmin"
password = var.db_password
multi_az = true
deletion_protection = true
skip_final_snapshot = false
backup_retention_period = 7
vpc_security_group_ids = [aws_security_group.rds.id]
db_subnet_group_name = aws_db_subnet_group.main.name
}CloudFront CDN
CloudFront serves S3 files from 450+ edge locations worldwide — 10-100x faster than serving directly from S3:
import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront'
const cf = new CloudFrontClient({ region: 'us-east-1' })
async function invalidateCache(paths: string[]): Promise<void> {
await cf.send(new CreateInvalidationCommand({
DistributionId: process.env.CLOUDFRONT_DISTRIBUTION_ID!,
InvalidationBatch: {
CallerReference: Date.now().toString(),
Paths: { Quantity: paths.length, Items: paths },
},
}))
}
// Invalidate everything after a deploy
await invalidateCache(['/*'])AWS CDK: Infrastructure as TypeScript
// cdk/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 rds from 'aws-cdk-lib/aws-rds'
import * as ec2 from 'aws-cdk-lib/aws-ec2'
class AppStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props)
const vpc = new ec2.Vpc(this, 'AppVPC', { maxAzs: 2 })
const assetsBucket = new s3.Bucket(this, 'Assets', {
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
versioned: true,
})
const distribution = new cloudfront.Distribution(this, 'CDN', {
defaultBehavior: {
origin: new origins.S3Origin(assetsBucket),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
},
})
const db = new rds.DatabaseInstance(this, 'AppDB', {
engine: rds.DatabaseInstanceEngine.postgres({
version: rds.PostgresEngineVersion.VER_16,
}),
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM),
vpc,
multiAz: true,
storageEncrypted: true,
deletionProtection: true,
})
}
}Common Mistakes
- Leaving NAT Gateways running in unused VPCs — costs $32/month each even with zero traffic
- Serving S3 directly without CloudFront — slower and generates unnecessary data-transfer costs
- Oversized RDS instances — use AWS Compute Optimizer to right-size; most teams start on db.t3.micro then scale
- No multi-AZ for production RDS — a single AZ failure will cause downtime without it
- Hardcoded AWS credentials — always use IAM roles for EC2/Lambda/ECS and Secrets Manager for application secrets
Best Practices
- Use IAM roles attached to compute resources (not access keys) for all AWS API calls
- Store secrets in AWS Secrets Manager and rotate them automatically
- Enable CloudTrail and GuardDuty in every account for audit and threat detection
- Use S3 Versioning and lifecycle policies to manage backup storage costs automatically
- Set AWS Budget alerts at 80% and 100% to catch runaway costs early
Key Takeaways
- S3 presigned URLs let browsers upload directly to S3 without routing files through your server, saving bandwidth and compute cost
- Lambda on arm64 architecture is 20% cheaper than x86 and has similar or faster cold-start performance
- RDS Multi-AZ gives automatic failover in under 60 seconds; enable it for all production databases
- CloudFront caches S3 content at 450+ edge locations, reducing latency from hundreds of milliseconds to single-digit milliseconds for most users
- AWS CDK lets you define infrastructure in TypeScript with full type safety, code reuse, and testing capabilities
- Free tier covers 1M Lambda requests/month, 5GB S3 storage, 750 hours t2.micro EC2, and 750 hours RDS t2.micro per month
- Always tag every AWS resource with Environment, Team, and Project tags to enable cost allocation reporting
- Use Secrets Manager instead of hardcoded environment variables — it handles rotation automatically
Advertisement
Related reading
AWS EC2 Complete Guide — Launch, Scale, and Secure Virtual Machines5 min readAWS S3 — Object Storage, Static Hosting, and CDN Guide4 min readAWS Lambda — Serverless Functions Complete Guide5 min readKubernetes on AWS EKS — Complete Setup Guide 20266 min readTerraform Complete Guide — Infrastructure as Code for Cloud Engineers5 min readAWS RDS — Managed Relational Database Complete Guide5 min read