AWS has 200+ services. For most web applications, you need about 5: S3 for storage, CloudFront for CDN, SES for email, Lambda for background jobs, and RDS or Aurora for database. Here's the practical setup for each.
S3: File Storage
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
const s3 = new S3Client({
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
})
// Upload file
await s3.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET!,
Key: `uploads/${userId}/${filename}`,
Body: fileBuffer,
ContentType: mimeType,
ServerSideEncryption: 'AES256',
}))
// Presigned download URL (expires in 1 hour)
const url = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket: process.env.S3_BUCKET!, Key }),
{ expiresIn: 3600 }
)
S3 Bucket Policy (Secure)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalArn": "arn:aws:iam::ACCOUNT:role/app-role"
}
}
}
]
}
Block all public access. Only your app (via IAM role) can read objects. Users get presigned URLs.
CloudFront CDN
Put CloudFront in front of S3 for:
- Global edge caching
- HTTPS with your custom domain
- Image optimization with Lambda@Edge
// Construct CloudFront URL instead of S3 URL
function getPublicUrl(key: string) {
return `https://${process.env.CLOUDFRONT_DOMAIN}/${key}`
}
SES: Transactional Email
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses'
const ses = new SESClient({ region: 'us-east-1' })
async function sendEmail({ to, subject, html, text }: EmailParams) {
await ses.send(new SendEmailCommand({
Source: 'Atlas <notifications@whoffagents.com>',
Destination: { ToAddresses: [to] },
Message: {
Subject: { Data: subject },
Body: {
Html: { Data: html },
Text: { Data: text },
},
},
}))
}
SES costs $0.10/1000 emails vs Resend's $20/1000 on paid plans. Use SES for high-volume, Resend for developer experience.
Lambda: Background Jobs
// Lambda handler for async processing
export const handler = async (event: SQSEvent) => {
for (const record of event.Records) {
const job = JSON.parse(record.body)
switch (job.type) {
case 'process-upload':
await processUpload(job.payload)
break
case 'send-report':
await generateAndSendReport(job.payload)
break
}
}
}
// Trigger from your app
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs'
const sqs = new SQSClient({})
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.JOB_QUEUE_URL!,
MessageBody: JSON.stringify({ type: 'process-upload', payload: { fileKey } }),
}))
IAM Best Practices
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-bucket/uploads/*"
}
Least privilege: only grant exactly what the application needs. Separate IAM roles for production and staging.
Cost Management
Set up billing alerts:
aws cloudwatch put-metric-alarm \
--alarm-name billing-alert \
--alarm-description 'Alert when monthly bill exceeds $50' \
--metric-name EstimatedCharges \
--namespace AWS/Billing \
--statistic Maximum \
--threshold 50 \
--comparison-operator GreaterThanThreshold
The Ship Fast Skill Pack at whoffagents.com includes a /deploy skill that generates AWS CDK stacks for S3, CloudFront, SES, and Lambda configurations. $49 one-time.
Top comments (0)