Amazon Q Developer - Kompletny Przewodnik po AI dla AWS
Czym jest Amazon Q Developer?
Amazon Q Developer (dawniej Amazon CodeWhisperer) to zaawansowany asystent AI od Amazon Web Services, zaprojektowany specjalnie dla programistów pracujących z ekosystemem AWS. W przeciwieństwie do innych asystentów AI, Amazon Q ma głęboką wiedzę o wszystkich serwisach AWS i potrafi generować kod, który jest zgodny z best practices AWS, bezpieczny i zoptymalizowany pod kątem kosztów.
Amazon Q to nie tylko autouzupełnianie kodu - to kompleksowe narzędzie do budowania, debugowania, optymalizacji i zabezpieczania aplikacji cloud-native. Integruje się bezpośrednio z AWS Console, IDE i CLI, oferując pomoc na każdym etapie rozwoju aplikacji.
Dlaczego Amazon Q Developer?
Kluczowe zalety Amazon Q
- Głęboka wiedza AWS - Zna wszystkie 200+ serwisów AWS
- Security scanning - Automatyczne wykrywanie luk bezpieczeństwa
- Code transformation - Automatyczna modernizacja kodu (np. Java 8→17)
- Infrastructure as Code - Generowanie CloudFormation, CDK, Terraform
- Cost optimization - Sugestie optymalizacji kosztów
- Bezpłatny tier - Darmowy dostęp dla indywidualnych deweloperów
- Compliance - Zgodność z SOC 2, HIPAA, FedRAMP
Amazon Q vs inne AI assistants dla cloud
| Aspekt | Amazon Q | GitHub Copilot | Google Cloud AI | Azure AI |
|---|---|---|---|---|
| AWS Integration | Natywna | Brak | Brak | Brak |
| Security Scanning | Wbudowane | Osobny produkt | Ograniczone | Ograniczone |
| IaC Generation | CloudFormation, CDK | Podstawowe | Terraform | ARM, Bicep |
| Cost Analysis | Tak | Nie | Nie | Nie |
| Free Tier | Tak | Nie | Tak | Tak |
| Console Integration | Pełna | Brak | Pełna | Pełna |
Instalacja i Konfiguracja
VS Code
# 1. Otwórz VS Code
# 2. Extensions (Ctrl+Shift+X)
# 3. Szukaj "Amazon Q"
# 4. Zainstaluj "Amazon Q"
# Lub z terminala:
code --install-extension amazonwebservices.amazon-q-vscodeJetBrains IDEs
# 1. File → Settings → Plugins
# 2. Marketplace → Szukaj "Amazon Q"
# 3. Install → Restart IDEVisual Studio
# 1. Extensions → Manage Extensions
# 2. Online → Szukaj "AWS Toolkit"
# 3. Download → Restart VSAWS CLI Integration
# Instalacja AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# Konfiguracja
aws configure
# AWS Access Key ID: YOUR_ACCESS_KEY
# AWS Secret Access Key: YOUR_SECRET_KEY
# Default region name: eu-central-1
# Default output format: json
# Instalacja Amazon Q CLI
pip install amazon-q-developer-cliAutoryzacja
# Opcja 1: AWS Builder ID (darmowe)
# - Kliknij "Sign in" w rozszerzeniu
# - Wybierz "Use personal email"
# - Utwórz AWS Builder ID
# Opcja 2: AWS IAM Identity Center (enterprise)
# - Kliknij "Sign in"
# - Wybierz "Use IAM Identity Center"
# - Wprowadź Start URL organizacjiGenerowanie Kodu AWS
Lambda Functions
// Amazon Q rozumie kontekst Lambda i AWS SDK
// Prompt: "Create Lambda function to process S3 events"
import { S3Event, Context } from 'aws-lambda'
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'
const s3Client = new S3Client({ region: process.env.AWS_REGION })
export const handler = async (event: S3Event, context: Context) => {
console.log('Processing S3 event:', JSON.stringify(event, null, 2))
for (const record of event.Records) {
const bucket = record.s3.bucket.name
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '))
try {
// Pobierz obiekt z S3
const getCommand = new GetObjectCommand({ Bucket: bucket, Key: key })
const response = await s3Client.send(getCommand)
// Przetwórz dane
const body = await response.Body?.transformToString()
const processedData = processData(body)
// Zapisz przetworzony plik
const putCommand = new PutObjectCommand({
Bucket: bucket,
Key: `processed/${key}`,
Body: JSON.stringify(processedData),
ContentType: 'application/json'
})
await s3Client.send(putCommand)
console.log(`Successfully processed: ${key}`)
} catch (error) {
console.error(`Error processing ${key}:`, error)
throw error
}
}
return {
statusCode: 200,
body: JSON.stringify({ message: 'Processing complete' })
}
}
function processData(data: string | undefined): object {
if (!data) return {}
// Amazon Q sugeruje logikę przetwarzania
return JSON.parse(data)
}DynamoDB Operations
# Amazon Q zna best practices DynamoDB
# Prompt: "Create DynamoDB CRUD operations with optimistic locking"
import boto3
from boto3.dynamodb.conditions import Key, Attr
from botocore.exceptions import ClientError
from decimal import Decimal
import json
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Users')
def create_user(user_data: dict) -> dict:
"""Create a new user with initial version."""
user_data['version'] = 1
user_data['created_at'] = datetime.now().isoformat()
user_data['updated_at'] = datetime.now().isoformat()
try:
table.put_item(
Item=user_data,
ConditionExpression='attribute_not_exists(user_id)'
)
return {'success': True, 'user': user_data}
except ClientError as e:
if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
return {'success': False, 'error': 'User already exists'}
raise
def get_user(user_id: str) -> dict:
"""Get user by ID with consistent read."""
response = table.get_item(
Key={'user_id': user_id},
ConsistentRead=True
)
return response.get('Item')
def update_user(user_id: str, updates: dict, current_version: int) -> dict:
"""Update user with optimistic locking."""
try:
# Build update expression dynamically
update_expr = 'SET version = version + :inc, updated_at = :updated'
expr_values = {
':inc': 1,
':updated': datetime.now().isoformat(),
':current_version': current_version
}
expr_names = {}
for key, value in updates.items():
safe_key = f'#{key}'
update_expr += f', {safe_key} = :{key}'
expr_values[f':{key}'] = value
expr_names[safe_key] = key
response = table.update_item(
Key={'user_id': user_id},
UpdateExpression=update_expr,
ExpressionAttributeValues=expr_values,
ExpressionAttributeNames=expr_names if expr_names else None,
ConditionExpression='version = :current_version',
ReturnValues='ALL_NEW'
)
return {'success': True, 'user': response['Attributes']}
except ClientError as e:
if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
return {'success': False, 'error': 'Version conflict - retry required'}
raise
def query_users_by_email_domain(domain: str) -> list:
"""Query users by email domain using GSI."""
response = table.query(
IndexName='email-domain-index',
KeyConditionExpression=Key('email_domain').eq(domain),
FilterExpression=Attr('active').eq(True)
)
return response['Items']
def batch_get_users(user_ids: list) -> list:
"""Batch get multiple users efficiently."""
# DynamoDB limit: 100 items per batch
results = []
for i in range(0, len(user_ids), 100):
batch = user_ids[i:i+100]
response = dynamodb.batch_get_item(
RequestItems={
'Users': {
'Keys': [{'user_id': uid} for uid in batch],
'ConsistentRead': True
}
}
)
results.extend(response['Responses']['Users'])
# Handle unprocessed keys
while response.get('UnprocessedKeys'):
response = dynamodb.batch_get_item(
RequestItems=response['UnprocessedKeys']
)
results.extend(response['Responses']['Users'])
return resultsAPI Gateway + Lambda
// Amazon Q generuje kompletne API z walidacją
// Prompt: "Create REST API with API Gateway and Lambda"
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context
} from 'aws-lambda'
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import {
DynamoDBDocumentClient,
GetCommand,
PutCommand,
UpdateCommand,
DeleteCommand,
QueryCommand
} from '@aws-sdk/lib-dynamodb'
const client = new DynamoDBClient({})
const docClient = DynamoDBDocumentClient.from(client)
const TABLE_NAME = process.env.TABLE_NAME!
// Middleware dla CORS i error handling
const withMiddleware = (
handler: (event: APIGatewayProxyEvent) => Promise<any>
) => {
return async (
event: APIGatewayProxyEvent,
context: Context
): Promise<APIGatewayProxyResult> => {
const headers = {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type,Authorization',
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS'
}
// Handle preflight
if (event.httpMethod === 'OPTIONS') {
return { statusCode: 200, headers, body: '' }
}
try {
const result = await handler(event)
return {
statusCode: result.statusCode || 200,
headers,
body: JSON.stringify(result.body)
}
} catch (error: any) {
console.error('Error:', error)
return {
statusCode: error.statusCode || 500,
headers,
body: JSON.stringify({
error: error.message || 'Internal Server Error'
})
}
}
}
}
// GET /users/{id}
export const getUser = withMiddleware(async (event) => {
const userId = event.pathParameters?.id
if (!userId) {
throw { statusCode: 400, message: 'User ID is required' }
}
const command = new GetCommand({
TableName: TABLE_NAME,
Key: { pk: `USER#${userId}`, sk: `USER#${userId}` }
})
const result = await docClient.send(command)
if (!result.Item) {
throw { statusCode: 404, message: 'User not found' }
}
return { body: result.Item }
})
// POST /users
export const createUser = withMiddleware(async (event) => {
const body = JSON.parse(event.body || '{}')
// Walidacja
if (!body.email || !body.name) {
throw { statusCode: 400, message: 'Email and name are required' }
}
const userId = generateId()
const timestamp = new Date().toISOString()
const user = {
pk: `USER#${userId}`,
sk: `USER#${userId}`,
id: userId,
email: body.email,
name: body.name,
createdAt: timestamp,
updatedAt: timestamp,
GSI1PK: `EMAIL#${body.email}`,
GSI1SK: `USER#${userId}`
}
const command = new PutCommand({
TableName: TABLE_NAME,
Item: user,
ConditionExpression: 'attribute_not_exists(pk)'
})
await docClient.send(command)
return { statusCode: 201, body: user }
})
// PUT /users/{id}
export const updateUser = withMiddleware(async (event) => {
const userId = event.pathParameters?.id
const body = JSON.parse(event.body || '{}')
if (!userId) {
throw { statusCode: 400, message: 'User ID is required' }
}
const updateExpressions: string[] = []
const expressionValues: Record<string, any> = {}
const expressionNames: Record<string, string> = {}
if (body.name) {
updateExpressions.push('#name = :name')
expressionValues[':name'] = body.name
expressionNames['#name'] = 'name'
}
if (body.email) {
updateExpressions.push('email = :email')
expressionValues[':email'] = body.email
}
updateExpressions.push('updatedAt = :updatedAt')
expressionValues[':updatedAt'] = new Date().toISOString()
const command = new UpdateCommand({
TableName: TABLE_NAME,
Key: { pk: `USER#${userId}`, sk: `USER#${userId}` },
UpdateExpression: `SET ${updateExpressions.join(', ')}`,
ExpressionAttributeValues: expressionValues,
ExpressionAttributeNames: Object.keys(expressionNames).length > 0
? expressionNames
: undefined,
ReturnValues: 'ALL_NEW',
ConditionExpression: 'attribute_exists(pk)'
})
const result = await docClient.send(command)
return { body: result.Attributes }
})
// DELETE /users/{id}
export const deleteUser = withMiddleware(async (event) => {
const userId = event.pathParameters?.id
if (!userId) {
throw { statusCode: 400, message: 'User ID is required' }
}
const command = new DeleteCommand({
TableName: TABLE_NAME,
Key: { pk: `USER#${userId}`, sk: `USER#${userId}` },
ConditionExpression: 'attribute_exists(pk)'
})
await docClient.send(command)
return { statusCode: 204, body: null }
})
function generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
}Infrastructure as Code
CloudFormation
# Amazon Q generuje kompletne szablony CloudFormation
# Prompt: "Create CloudFormation template for serverless API"
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Serverless API with Lambda, API Gateway, and DynamoDB
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- prod
TableReadCapacity:
Type: Number
Default: 5
TableWriteCapacity:
Type: Number
Default: 5
Globals:
Function:
Runtime: nodejs18.x
Timeout: 30
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref UsersTable
ENVIRONMENT: !Ref Environment
Tracing: Active
Resources:
# DynamoDB Table
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub ${AWS::StackName}-users
BillingMode: PROVISIONED
ProvisionedThroughput:
ReadCapacityUnits: !Ref TableReadCapacity
WriteCapacityUnits: !Ref TableWriteCapacity
AttributeDefinitions:
- AttributeName: pk
AttributeType: S
- AttributeName: sk
AttributeType: S
- AttributeName: GSI1PK
AttributeType: S
- AttributeName: GSI1SK
AttributeType: S
KeySchema:
- AttributeName: pk
KeyType: HASH
- AttributeName: sk
KeyType: RANGE
GlobalSecondaryIndexes:
- IndexName: GSI1
KeySchema:
- AttributeName: GSI1PK
KeyType: HASH
- AttributeName: GSI1SK
KeyType: RANGE
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: !Ref TableReadCapacity
WriteCapacityUnits: !Ref TableWriteCapacity
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: true
SSESpecification:
SSEEnabled: true
# API Gateway
ApiGateway:
Type: AWS::Serverless::Api
Properties:
Name: !Sub ${AWS::StackName}-api
StageName: !Ref Environment
Cors:
AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
AllowHeaders: "'Content-Type,Authorization'"
AllowOrigin: "'*'"
Auth:
DefaultAuthorizer: CognitoAuthorizer
Authorizers:
CognitoAuthorizer:
UserPoolArn: !GetAtt UserPool.Arn
# Lambda Functions
GetUserFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-get-user
Handler: dist/handlers/users.getUser
CodeUri: ./
Policies:
- DynamoDBReadPolicy:
TableName: !Ref UsersTable
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users/{id}
Method: GET
CreateUserFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-create-user
Handler: dist/handlers/users.createUser
CodeUri: ./
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users
Method: POST
UpdateUserFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-update-user
Handler: dist/handlers/users.updateUser
CodeUri: ./
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users/{id}
Method: PUT
DeleteUserFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-delete-user
Handler: dist/handlers/users.deleteUser
CodeUri: ./
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users/{id}
Method: DELETE
# Cognito User Pool
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Sub ${AWS::StackName}-users
AutoVerifiedAttributes:
- email
UsernameAttributes:
- email
Policies:
PasswordPolicy:
MinimumLength: 8
RequireLowercase: true
RequireNumbers: true
RequireSymbols: true
RequireUppercase: true
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: !Sub ${AWS::StackName}-client
UserPoolId: !Ref UserPool
GenerateSecret: false
ExplicitAuthFlows:
- ALLOW_USER_PASSWORD_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
Outputs:
ApiUrl:
Description: API Gateway URL
Value: !Sub https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/${Environment}
Export:
Name: !Sub ${AWS::StackName}-api-url
TableName:
Description: DynamoDB Table Name
Value: !Ref UsersTable
Export:
Name: !Sub ${AWS::StackName}-table-name
UserPoolId:
Description: Cognito User Pool ID
Value: !Ref UserPool
Export:
Name: !Sub ${AWS::StackName}-user-pool-id
UserPoolClientId:
Description: Cognito User Pool Client ID
Value: !Ref UserPoolClient
Export:
Name: !Sub ${AWS::StackName}-user-pool-client-idAWS CDK
// Amazon Q generuje kompletne staki CDK
// Prompt: "Create CDK stack for ECS Fargate service"
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as ecs from 'aws-cdk-lib/aws-ecs'
import * as ecr from 'aws-cdk-lib/aws-ecr'
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'
import * as logs from 'aws-cdk-lib/aws-logs'
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'
import * as rds from 'aws-cdk-lib/aws-rds'
interface ApiStackProps extends cdk.StackProps {
environment: 'dev' | 'staging' | 'prod'
domainName?: string
}
export class ApiStack extends cdk.Stack {
public readonly loadBalancer: elbv2.ApplicationLoadBalancer
public readonly service: ecs.FargateService
constructor(scope: Construct, id: string, props: ApiStackProps) {
super(scope, id, props)
// VPC
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 3,
natGateways: props.environment === 'prod' ? 3 : 1,
subnetConfiguration: [
{
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
cidrMask: 24
},
{
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
cidrMask: 24
},
{
name: 'Isolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
cidrMask: 24
}
]
})
// Security Groups
const albSecurityGroup = new ec2.SecurityGroup(this, 'AlbSg', {
vpc,
description: 'ALB Security Group',
allowAllOutbound: true
})
albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(443),
'HTTPS'
)
albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(80),
'HTTP redirect'
)
const ecsSecurityGroup = new ec2.SecurityGroup(this, 'EcsSg', {
vpc,
description: 'ECS Security Group',
allowAllOutbound: true
})
ecsSecurityGroup.addIngressRule(
albSecurityGroup,
ec2.Port.tcp(3000),
'From ALB'
)
const dbSecurityGroup = new ec2.SecurityGroup(this, 'DbSg', {
vpc,
description: 'RDS Security Group'
})
dbSecurityGroup.addIngressRule(
ecsSecurityGroup,
ec2.Port.tcp(5432),
'From ECS'
)
// RDS PostgreSQL
const dbSecret = new secretsmanager.Secret(this, 'DbSecret', {
secretName: `${id}/database`,
generateSecretString: {
secretStringTemplate: JSON.stringify({ username: 'admin' }),
generateStringKey: 'password',
excludePunctuation: true
}
})
const database = new rds.DatabaseInstance(this, 'Database', {
engine: rds.DatabaseInstanceEngine.postgres({
version: rds.PostgresEngineVersion.VER_15
}),
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
props.environment === 'prod'
? ec2.InstanceSize.MEDIUM
: ec2.InstanceSize.MICRO
),
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
securityGroups: [dbSecurityGroup],
credentials: rds.Credentials.fromSecret(dbSecret),
multiAz: props.environment === 'prod',
allocatedStorage: 20,
maxAllocatedStorage: 100,
deletionProtection: props.environment === 'prod',
removalPolicy: props.environment === 'prod'
? cdk.RemovalPolicy.RETAIN
: cdk.RemovalPolicy.DESTROY
})
// ECS Cluster
const cluster = new ecs.Cluster(this, 'Cluster', {
vpc,
containerInsights: true,
enableFargateCapacityProviders: true
})
// ECR Repository
const repository = new ecr.Repository(this, 'Repository', {
repositoryName: `${id.toLowerCase()}-api`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
imageScanOnPush: true,
lifecycleRules: [
{
maxImageCount: 10,
rulePriority: 1
}
]
})
// Task Definition
const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
memoryLimitMiB: props.environment === 'prod' ? 2048 : 512,
cpu: props.environment === 'prod' ? 1024 : 256
})
const container = taskDefinition.addContainer('api', {
image: ecs.ContainerImage.fromEcrRepository(repository, 'latest'),
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'api',
logRetention: logs.RetentionDays.ONE_MONTH
}),
environment: {
NODE_ENV: props.environment,
PORT: '3000'
},
secrets: {
DATABASE_URL: ecs.Secret.fromSecretsManager(database.secret!),
DB_HOST: ecs.Secret.fromSecretsManager(database.secret!, 'host'),
DB_PASSWORD: ecs.Secret.fromSecretsManager(database.secret!, 'password')
},
healthCheck: {
command: ['CMD-SHELL', 'curl -f http://localhost:3000/health || exit 1'],
interval: cdk.Duration.seconds(30),
timeout: cdk.Duration.seconds(5),
retries: 3
}
})
container.addPortMappings({ containerPort: 3000 })
// Fargate Service
this.service = new ecs.FargateService(this, 'Service', {
cluster,
taskDefinition,
desiredCount: props.environment === 'prod' ? 3 : 1,
securityGroups: [ecsSecurityGroup],
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
capacityProviderStrategies: [
{
capacityProvider: 'FARGATE_SPOT',
weight: props.environment === 'prod' ? 1 : 2
},
{
capacityProvider: 'FARGATE',
weight: props.environment === 'prod' ? 2 : 1
}
],
circuitBreaker: { rollback: true },
enableExecuteCommand: true
})
// Auto Scaling
const scaling = this.service.autoScaleTaskCount({
minCapacity: props.environment === 'prod' ? 3 : 1,
maxCapacity: props.environment === 'prod' ? 20 : 5
})
scaling.scaleOnCpuUtilization('CpuScaling', {
targetUtilizationPercent: 70,
scaleInCooldown: cdk.Duration.seconds(60),
scaleOutCooldown: cdk.Duration.seconds(60)
})
scaling.scaleOnMemoryUtilization('MemoryScaling', {
targetUtilizationPercent: 70
})
// Application Load Balancer
this.loadBalancer = new elbv2.ApplicationLoadBalancer(this, 'Alb', {
vpc,
internetFacing: true,
securityGroup: albSecurityGroup
})
const listener = this.loadBalancer.addListener('HttpsListener', {
port: 443,
certificates: props.domainName
? [/* Add certificate */]
: undefined,
protocol: props.domainName
? elbv2.ApplicationProtocol.HTTPS
: elbv2.ApplicationProtocol.HTTP
})
listener.addTargets('EcsTarget', {
port: 3000,
targets: [this.service],
healthCheck: {
path: '/health',
interval: cdk.Duration.seconds(30),
healthyThresholdCount: 2,
unhealthyThresholdCount: 3
},
deregistrationDelay: cdk.Duration.seconds(30)
})
// HTTP to HTTPS redirect
if (props.domainName) {
this.loadBalancer.addListener('HttpListener', {
port: 80,
defaultAction: elbv2.ListenerAction.redirect({
port: '443',
protocol: 'HTTPS',
permanent: true
})
})
}
// Outputs
new cdk.CfnOutput(this, 'LoadBalancerDns', {
value: this.loadBalancer.loadBalancerDnsName
})
new cdk.CfnOutput(this, 'RepositoryUri', {
value: repository.repositoryUri
})
}
}Security Scanning
Automatyczne skanowanie kodu
// Amazon Q automatycznie wykrywa problemy bezpieczeństwa
// ⚠️ Amazon Q ostrzega:
// SQL Injection vulnerability detected
const query = `SELECT * FROM users WHERE id = '${userId}'`
// ✅ Amazon Q sugeruje poprawkę:
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb'
const command = new GetCommand({
TableName: 'users',
Key: { id: userId }
})
const result = await docClient.send(command)Typy wykrywanych problemów
┌─────────────────────────────────────────────────────────┐
│ AMAZON Q SECURITY SCANNING │
├─────────────────────────────────────────────────────────┤
│ │
│ ⚠️ Injection Vulnerabilities │
│ └─ SQL, NoSQL, Command, LDAP injection │
│ │
│ ⚠️ Authentication Issues │
│ └─ Hardcoded credentials, weak passwords │
│ │
│ ⚠️ Sensitive Data Exposure │
│ └─ API keys, secrets in code │
│ │
│ ⚠️ Security Misconfiguration │
│ └─ Open S3 buckets, public resources │
│ │
│ ⚠️ Cross-Site Scripting (XSS) │
│ └─ Unescaped user input │
│ │
│ ⚠️ Insecure Dependencies │
│ └─ Known CVEs in packages │
│ │
└─────────────────────────────────────────────────────────┘Skanowanie w CI/CD
# .github/workflows/security-scan.yml
name: Security Scan
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
amazon-q-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-central-1
- name: Run Amazon Q Security Scan
run: |
pip install amazon-q-developer-cli
amazon-q scan --format sarif --output results.sarif
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: results.sarif
- name: Fail on high severity
run: |
HIGH_COUNT=$(jq '.runs[0].results | map(select(.level == "error")) | length' results.sarif)
if [ "$HIGH_COUNT" -gt 0 ]; then
echo "Found $HIGH_COUNT high severity issues"
exit 1
fiCode Transformation
Java Modernization
// Amazon Q może automatycznie zmodernizować kod Java
// PRZED (Java 8):
public class UserService {
private List<User> users = new ArrayList<>();
public List<User> findActiveUsers() {
List<User> result = new ArrayList<>();
for (User user : users) {
if (user.isActive()) {
result.add(user);
}
}
return result;
}
public Optional<User> findByEmail(String email) {
for (User user : users) {
if (user.getEmail().equals(email)) {
return Optional.of(user);
}
}
return Optional.empty();
}
}
// PO (Java 17) - Amazon Q transformation:
public class UserService {
private final List<User> users = new ArrayList<>();
public List<User> findActiveUsers() {
return users.stream()
.filter(User::isActive)
.toList();
}
public Optional<User> findByEmail(String email) {
return users.stream()
.filter(user -> user.getEmail().equals(email))
.findFirst();
}
}.NET Modernization
// Amazon Q modernizuje .NET Framework do .NET 8
// PRZED (.NET Framework 4.8):
public class DataService
{
public async Task<List<Customer>> GetCustomersAsync()
{
using (var client = new HttpClient())
{
var response = await client.GetAsync("https://api.example.com/customers");
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<Customer>>(content);
}
}
}
// PO (.NET 8) - Amazon Q transformation:
public class DataService(HttpClient httpClient)
{
public async Task<List<Customer>> GetCustomersAsync()
{
return await httpClient.GetFromJsonAsync<List<Customer>>(
"https://api.example.com/customers"
) ?? [];
}
}Amazon Q w AWS Console
Chat w Console
User: How do I set up a VPC with public and private subnets?
Amazon Q:
I'll help you create a VPC with public and private subnets. Here's what you need:
1. **VPC**: Create a VPC with CIDR block (e.g., 10.0.0.0/16)
2. **Subnets**:
- Public: 10.0.1.0/24 (for ALB, NAT Gateway)
- Private: 10.0.2.0/24 (for EC2, Lambda)
3. **Internet Gateway**: Attach to VPC for public internet access
4. **NAT Gateway**: Place in public subnet for private subnet outbound access
5. **Route Tables**:
- Public: Route 0.0.0.0/0 → Internet Gateway
- Private: Route 0.0.0.0/0 → NAT Gateway
Would you like me to create a CloudFormation template for this setup?Troubleshooting z Amazon Q
User: My Lambda function is timing out when connecting to RDS
Amazon Q:
Based on the symptoms, here are the most common causes and solutions:
**1. VPC Configuration Issue (Most Common)**
- Lambda needs to be in the same VPC as RDS
- Check Security Groups allow traffic on port 5432/3306
- Ensure Lambda has a route to RDS subnet
**2. Security Group Configuration**RDS Security Group should allow:
Inbound: Port 5432 from Lambda Security Group
**3. NAT Gateway for Internet Access**
If Lambda needs both RDS and internet access, ensure:
- Lambda is in private subnet
- NAT Gateway is in public subnet
- Route table has 0.0.0.0/0 → NAT Gateway
**4. Connection Pool Exhaustion**
```python
# Use connection pooling
from sqlalchemy import create_engine
engine = create_engine(DATABASE_URL, pool_size=5, max_overflow=10)5. Increase Timeout
# SAM template
Timeout: 30 # Increase from default 3 secondsWould you like me to help diagnose which specific issue you're facing?
## Cennik
### Free Tier
**Cena**: $0/miesiąc
**Zawiera:**
- 50 security scans/miesiąc
- Podstawowe code suggestions
- Integracja z VS Code, JetBrains
- Dostęp do Amazon Q Chat
- AWS Console integration
### Pro Tier
**Cena**: $19/użytkownika/miesiąc
**Zawiera:**
- Wszystko z Free
- Nieograniczone security scans
- Code transformation
- Administrative controls
- Higher quotas
- Priority support
### Enterprise
**Cena**: Custom
**Zawiera:**
- Wszystko z Pro
- SSO/SAML
- Customization options
- Dedicated support
- SLA guarantees
- Compliance reporting
## Best Practices
### Efektywne prompty
```typescript
// ❌ Zbyt ogólne
// "Create Lambda function"
// ✅ Szczegółowe
// "Create Lambda function that:
// - Processes S3 events for image uploads
// - Resizes images to 800x600
// - Stores thumbnails in a separate bucket
// - Uses Sharp library for image processing
// - Includes error handling and CloudWatch logging"Konfiguracja projektu
// .amazonq/config.json
{
"codeGeneration": {
"preferredLanguage": "typescript",
"preferredRuntime": "nodejs18.x",
"preferredRegion": "eu-central-1"
},
"securityScanning": {
"enabled": true,
"severity": "HIGH",
"autoFix": false
},
"infraAsCode": {
"preferredTool": "cdk",
"language": "typescript"
}
}FAQ
Czy Amazon Q jest darmowy?
Tak, Amazon Q ma darmowy tier z 50 security scans miesięcznie i podstawowymi sugestiami kodu. Pro tier kosztuje $19/użytkownika/miesiąc.
Czy Amazon Q działa tylko z AWS?
Amazon Q jest zoptymalizowany dla AWS, ale może generować ogólny kod w wielu językach. Jednak jego największa wartość to głęboka integracja z ekosystemem AWS.
Jak Amazon Q różni się od Copilot?
Amazon Q specjalizuje się w AWS - zna wszystkie serwisy, best practices, security patterns. Copilot jest bardziej uniwersalny, ale nie ma tak głębokiej wiedzy o AWS.
Czy mogę używać Amazon Q offline?
Nie, Amazon Q wymaga połączenia z internetem do generowania sugestii i skanowania bezpieczeństwa.
Podsumowanie
Amazon Q Developer to niezbędne narzędzie dla każdego programisty pracującego z AWS. Dzięki głębokiej wiedzy o serwisach AWS, wbudowanemu skanowaniu bezpieczeństwa i możliwościom transformacji kodu, Amazon Q znacząco przyspiesza rozwój aplikacji cloud-native.
Kluczowe zalety:
- Głęboka integracja AWS - Zna wszystkie 200+ serwisów
- Security-first - Automatyczne wykrywanie luk
- IaC generation - CloudFormation, CDK, Terraform
- Code modernization - Java, .NET transformation
- Darmowy tier - Dostępny dla wszystkich
Jeśli budujesz na AWS, Amazon Q powinien być częścią Twojego toolkit.
Amazon Q Developer - complete guide to AI for AWS
What is Amazon Q Developer?
Amazon Q Developer (formerly Amazon CodeWhisperer) is an advanced AI assistant from Amazon Web Services, designed specifically for developers working with the AWS ecosystem. Unlike other AI assistants, Amazon Q has deep knowledge of all AWS services and can generate code that follows AWS best practices, is secure, and is optimized for cost.
Amazon Q is not just code autocomplete - it is a comprehensive tool for building, debugging, optimizing, and securing cloud-native applications. It integrates directly with the AWS Console, IDEs, and CLI, providing assistance at every stage of application development.
Why Amazon Q Developer?
Key advantages of Amazon Q
- Deep AWS knowledge - Knows all 200+ AWS services
- Security scanning - Automatic vulnerability detection
- Code transformation - Automatic code modernization (e.g., Java 8 to 17)
- Infrastructure as Code - Generation of CloudFormation, CDK, Terraform
- Cost optimization - Cost optimization suggestions
- Free tier - Free access for individual developers
- Compliance - SOC 2, HIPAA, FedRAMP compliant
Amazon Q vs other AI assistants for cloud
| Aspect | Amazon Q | GitHub Copilot | Google Cloud AI | Azure AI |
|---|---|---|---|---|
| AWS Integration | Native | None | None | None |
| Security Scanning | Built-in | Separate product | Limited | Limited |
| IaC Generation | CloudFormation, CDK | Basic | Terraform | ARM, Bicep |
| Cost Analysis | Yes | No | No | No |
| Free Tier | Yes | No | Yes | Yes |
| Console Integration | Full | None | Full | Full |
Installation and configuration
VS Code
# 1. Open VS Code
# 2. Extensions (Ctrl+Shift+X)
# 3. Search "Amazon Q"
# 4. Install "Amazon Q"
# Or from the terminal:
code --install-extension amazonwebservices.amazon-q-vscodeJetBrains IDEs
# 1. File → Settings → Plugins
# 2. Marketplace → Search "Amazon Q"
# 3. Install → Restart IDEVisual Studio
# 1. Extensions → Manage Extensions
# 2. Online → Search "AWS Toolkit"
# 3. Download → Restart VSAWS CLI integration
# Install AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# Configuration
aws configure
# AWS Access Key ID: YOUR_ACCESS_KEY
# AWS Secret Access Key: YOUR_SECRET_KEY
# Default region name: eu-central-1
# Default output format: json
# Install Amazon Q CLI
pip install amazon-q-developer-cliAuthorization
# Option 1: AWS Builder ID (free)
# - Click "Sign in" in the extension
# - Select "Use personal email"
# - Create an AWS Builder ID
# Option 2: AWS IAM Identity Center (enterprise)
# - Click "Sign in"
# - Select "Use IAM Identity Center"
# - Enter your organization's Start URLAWS code generation
Lambda Functions
// Amazon Q understands Lambda context and the AWS SDK
// Prompt: "Create Lambda function to process S3 events"
import { S3Event, Context } from 'aws-lambda'
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'
const s3Client = new S3Client({ region: process.env.AWS_REGION })
export const handler = async (event: S3Event, context: Context) => {
console.log('Processing S3 event:', JSON.stringify(event, null, 2))
for (const record of event.Records) {
const bucket = record.s3.bucket.name
const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '))
try {
// Get the object from S3
const getCommand = new GetObjectCommand({ Bucket: bucket, Key: key })
const response = await s3Client.send(getCommand)
// Process the data
const body = await response.Body?.transformToString()
const processedData = processData(body)
// Save the processed file
const putCommand = new PutObjectCommand({
Bucket: bucket,
Key: `processed/${key}`,
Body: JSON.stringify(processedData),
ContentType: 'application/json'
})
await s3Client.send(putCommand)
console.log(`Successfully processed: ${key}`)
} catch (error) {
console.error(`Error processing ${key}:`, error)
throw error
}
}
return {
statusCode: 200,
body: JSON.stringify({ message: 'Processing complete' })
}
}
function processData(data: string | undefined): object {
if (!data) return {}
// Amazon Q suggests processing logic
return JSON.parse(data)
}DynamoDB operations
# Amazon Q knows DynamoDB best practices
# Prompt: "Create DynamoDB CRUD operations with optimistic locking"
import boto3
from boto3.dynamodb.conditions import Key, Attr
from botocore.exceptions import ClientError
from decimal import Decimal
import json
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Users')
def create_user(user_data: dict) -> dict:
"""Create a new user with initial version."""
user_data['version'] = 1
user_data['created_at'] = datetime.now().isoformat()
user_data['updated_at'] = datetime.now().isoformat()
try:
table.put_item(
Item=user_data,
ConditionExpression='attribute_not_exists(user_id)'
)
return {'success': True, 'user': user_data}
except ClientError as e:
if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
return {'success': False, 'error': 'User already exists'}
raise
def get_user(user_id: str) -> dict:
"""Get user by ID with consistent read."""
response = table.get_item(
Key={'user_id': user_id},
ConsistentRead=True
)
return response.get('Item')
def update_user(user_id: str, updates: dict, current_version: int) -> dict:
"""Update user with optimistic locking."""
try:
# Build update expression dynamically
update_expr = 'SET version = version + :inc, updated_at = :updated'
expr_values = {
':inc': 1,
':updated': datetime.now().isoformat(),
':current_version': current_version
}
expr_names = {}
for key, value in updates.items():
safe_key = f'#{key}'
update_expr += f', {safe_key} = :{key}'
expr_values[f':{key}'] = value
expr_names[safe_key] = key
response = table.update_item(
Key={'user_id': user_id},
UpdateExpression=update_expr,
ExpressionAttributeValues=expr_values,
ExpressionAttributeNames=expr_names if expr_names else None,
ConditionExpression='version = :current_version',
ReturnValues='ALL_NEW'
)
return {'success': True, 'user': response['Attributes']}
except ClientError as e:
if e.response['Error']['Code'] == 'ConditionalCheckFailedException':
return {'success': False, 'error': 'Version conflict - retry required'}
raise
def query_users_by_email_domain(domain: str) -> list:
"""Query users by email domain using GSI."""
response = table.query(
IndexName='email-domain-index',
KeyConditionExpression=Key('email_domain').eq(domain),
FilterExpression=Attr('active').eq(True)
)
return response['Items']
def batch_get_users(user_ids: list) -> list:
"""Batch get multiple users efficiently."""
# DynamoDB limit: 100 items per batch
results = []
for i in range(0, len(user_ids), 100):
batch = user_ids[i:i+100]
response = dynamodb.batch_get_item(
RequestItems={
'Users': {
'Keys': [{'user_id': uid} for uid in batch],
'ConsistentRead': True
}
}
)
results.extend(response['Responses']['Users'])
# Handle unprocessed keys
while response.get('UnprocessedKeys'):
response = dynamodb.batch_get_item(
RequestItems=response['UnprocessedKeys']
)
results.extend(response['Responses']['Users'])
return resultsAPI Gateway + Lambda
// Amazon Q generates complete APIs with validation
// Prompt: "Create REST API with API Gateway and Lambda"
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context
} from 'aws-lambda'
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import {
DynamoDBDocumentClient,
GetCommand,
PutCommand,
UpdateCommand,
DeleteCommand,
QueryCommand
} from '@aws-sdk/lib-dynamodb'
const client = new DynamoDBClient({})
const docClient = DynamoDBDocumentClient.from(client)
const TABLE_NAME = process.env.TABLE_NAME!
// Middleware for CORS and error handling
const withMiddleware = (
handler: (event: APIGatewayProxyEvent) => Promise<any>
) => {
return async (
event: APIGatewayProxyEvent,
context: Context
): Promise<APIGatewayProxyResult> => {
const headers = {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type,Authorization',
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS'
}
// Handle preflight
if (event.httpMethod === 'OPTIONS') {
return { statusCode: 200, headers, body: '' }
}
try {
const result = await handler(event)
return {
statusCode: result.statusCode || 200,
headers,
body: JSON.stringify(result.body)
}
} catch (error: any) {
console.error('Error:', error)
return {
statusCode: error.statusCode || 500,
headers,
body: JSON.stringify({
error: error.message || 'Internal Server Error'
})
}
}
}
}
// GET /users/{id}
export const getUser = withMiddleware(async (event) => {
const userId = event.pathParameters?.id
if (!userId) {
throw { statusCode: 400, message: 'User ID is required' }
}
const command = new GetCommand({
TableName: TABLE_NAME,
Key: { pk: `USER#${userId}`, sk: `USER#${userId}` }
})
const result = await docClient.send(command)
if (!result.Item) {
throw { statusCode: 404, message: 'User not found' }
}
return { body: result.Item }
})
// POST /users
export const createUser = withMiddleware(async (event) => {
const body = JSON.parse(event.body || '{}')
// Validation
if (!body.email || !body.name) {
throw { statusCode: 400, message: 'Email and name are required' }
}
const userId = generateId()
const timestamp = new Date().toISOString()
const user = {
pk: `USER#${userId}`,
sk: `USER#${userId}`,
id: userId,
email: body.email,
name: body.name,
createdAt: timestamp,
updatedAt: timestamp,
GSI1PK: `EMAIL#${body.email}`,
GSI1SK: `USER#${userId}`
}
const command = new PutCommand({
TableName: TABLE_NAME,
Item: user,
ConditionExpression: 'attribute_not_exists(pk)'
})
await docClient.send(command)
return { statusCode: 201, body: user }
})
// PUT /users/{id}
export const updateUser = withMiddleware(async (event) => {
const userId = event.pathParameters?.id
const body = JSON.parse(event.body || '{}')
if (!userId) {
throw { statusCode: 400, message: 'User ID is required' }
}
const updateExpressions: string[] = []
const expressionValues: Record<string, any> = {}
const expressionNames: Record<string, string> = {}
if (body.name) {
updateExpressions.push('#name = :name')
expressionValues[':name'] = body.name
expressionNames['#name'] = 'name'
}
if (body.email) {
updateExpressions.push('email = :email')
expressionValues[':email'] = body.email
}
updateExpressions.push('updatedAt = :updatedAt')
expressionValues[':updatedAt'] = new Date().toISOString()
const command = new UpdateCommand({
TableName: TABLE_NAME,
Key: { pk: `USER#${userId}`, sk: `USER#${userId}` },
UpdateExpression: `SET ${updateExpressions.join(', ')}`,
ExpressionAttributeValues: expressionValues,
ExpressionAttributeNames: Object.keys(expressionNames).length > 0
? expressionNames
: undefined,
ReturnValues: 'ALL_NEW',
ConditionExpression: 'attribute_exists(pk)'
})
const result = await docClient.send(command)
return { body: result.Attributes }
})
// DELETE /users/{id}
export const deleteUser = withMiddleware(async (event) => {
const userId = event.pathParameters?.id
if (!userId) {
throw { statusCode: 400, message: 'User ID is required' }
}
const command = new DeleteCommand({
TableName: TABLE_NAME,
Key: { pk: `USER#${userId}`, sk: `USER#${userId}` },
ConditionExpression: 'attribute_exists(pk)'
})
await docClient.send(command)
return { statusCode: 204, body: null }
})
function generateId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
}Infrastructure as Code
CloudFormation
# Amazon Q generates complete CloudFormation templates
# Prompt: "Create CloudFormation template for serverless API"
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Serverless API with Lambda, API Gateway, and DynamoDB
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- prod
TableReadCapacity:
Type: Number
Default: 5
TableWriteCapacity:
Type: Number
Default: 5
Globals:
Function:
Runtime: nodejs18.x
Timeout: 30
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref UsersTable
ENVIRONMENT: !Ref Environment
Tracing: Active
Resources:
# DynamoDB Table
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub ${AWS::StackName}-users
BillingMode: PROVISIONED
ProvisionedThroughput:
ReadCapacityUnits: !Ref TableReadCapacity
WriteCapacityUnits: !Ref TableWriteCapacity
AttributeDefinitions:
- AttributeName: pk
AttributeType: S
- AttributeName: sk
AttributeType: S
- AttributeName: GSI1PK
AttributeType: S
- AttributeName: GSI1SK
AttributeType: S
KeySchema:
- AttributeName: pk
KeyType: HASH
- AttributeName: sk
KeyType: RANGE
GlobalSecondaryIndexes:
- IndexName: GSI1
KeySchema:
- AttributeName: GSI1PK
KeyType: HASH
- AttributeName: GSI1SK
KeyType: RANGE
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: !Ref TableReadCapacity
WriteCapacityUnits: !Ref TableWriteCapacity
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: true
SSESpecification:
SSEEnabled: true
# API Gateway
ApiGateway:
Type: AWS::Serverless::Api
Properties:
Name: !Sub ${AWS::StackName}-api
StageName: !Ref Environment
Cors:
AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
AllowHeaders: "'Content-Type,Authorization'"
AllowOrigin: "'*'"
Auth:
DefaultAuthorizer: CognitoAuthorizer
Authorizers:
CognitoAuthorizer:
UserPoolArn: !GetAtt UserPool.Arn
# Lambda Functions
GetUserFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-get-user
Handler: dist/handlers/users.getUser
CodeUri: ./
Policies:
- DynamoDBReadPolicy:
TableName: !Ref UsersTable
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users/{id}
Method: GET
CreateUserFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-create-user
Handler: dist/handlers/users.createUser
CodeUri: ./
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users
Method: POST
UpdateUserFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-update-user
Handler: dist/handlers/users.updateUser
CodeUri: ./
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users/{id}
Method: PUT
DeleteUserFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-delete-user
Handler: dist/handlers/users.deleteUser
CodeUri: ./
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Events:
Api:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users/{id}
Method: DELETE
# Cognito User Pool
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Sub ${AWS::StackName}-users
AutoVerifiedAttributes:
- email
UsernameAttributes:
- email
Policies:
PasswordPolicy:
MinimumLength: 8
RequireLowercase: true
RequireNumbers: true
RequireSymbols: true
RequireUppercase: true
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: !Sub ${AWS::StackName}-client
UserPoolId: !Ref UserPool
GenerateSecret: false
ExplicitAuthFlows:
- ALLOW_USER_PASSWORD_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
Outputs:
ApiUrl:
Description: API Gateway URL
Value: !Sub https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/${Environment}
Export:
Name: !Sub ${AWS::StackName}-api-url
TableName:
Description: DynamoDB Table Name
Value: !Ref UsersTable
Export:
Name: !Sub ${AWS::StackName}-table-name
UserPoolId:
Description: Cognito User Pool ID
Value: !Ref UserPool
Export:
Name: !Sub ${AWS::StackName}-user-pool-id
UserPoolClientId:
Description: Cognito User Pool Client ID
Value: !Ref UserPoolClient
Export:
Name: !Sub ${AWS::StackName}-user-pool-client-idAWS CDK
// Amazon Q generates complete CDK stacks
// Prompt: "Create CDK stack for ECS Fargate service"
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as ecs from 'aws-cdk-lib/aws-ecs'
import * as ecr from 'aws-cdk-lib/aws-ecr'
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'
import * as logs from 'aws-cdk-lib/aws-logs'
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'
import * as rds from 'aws-cdk-lib/aws-rds'
interface ApiStackProps extends cdk.StackProps {
environment: 'dev' | 'staging' | 'prod'
domainName?: string
}
export class ApiStack extends cdk.Stack {
public readonly loadBalancer: elbv2.ApplicationLoadBalancer
public readonly service: ecs.FargateService
constructor(scope: Construct, id: string, props: ApiStackProps) {
super(scope, id, props)
// VPC
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 3,
natGateways: props.environment === 'prod' ? 3 : 1,
subnetConfiguration: [
{
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
cidrMask: 24
},
{
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
cidrMask: 24
},
{
name: 'Isolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
cidrMask: 24
}
]
})
// Security Groups
const albSecurityGroup = new ec2.SecurityGroup(this, 'AlbSg', {
vpc,
description: 'ALB Security Group',
allowAllOutbound: true
})
albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(443),
'HTTPS'
)
albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(80),
'HTTP redirect'
)
const ecsSecurityGroup = new ec2.SecurityGroup(this, 'EcsSg', {
vpc,
description: 'ECS Security Group',
allowAllOutbound: true
})
ecsSecurityGroup.addIngressRule(
albSecurityGroup,
ec2.Port.tcp(3000),
'From ALB'
)
const dbSecurityGroup = new ec2.SecurityGroup(this, 'DbSg', {
vpc,
description: 'RDS Security Group'
})
dbSecurityGroup.addIngressRule(
ecsSecurityGroup,
ec2.Port.tcp(5432),
'From ECS'
)
// RDS PostgreSQL
const dbSecret = new secretsmanager.Secret(this, 'DbSecret', {
secretName: `${id}/database`,
generateSecretString: {
secretStringTemplate: JSON.stringify({ username: 'admin' }),
generateStringKey: 'password',
excludePunctuation: true
}
})
const database = new rds.DatabaseInstance(this, 'Database', {
engine: rds.DatabaseInstanceEngine.postgres({
version: rds.PostgresEngineVersion.VER_15
}),
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
props.environment === 'prod'
? ec2.InstanceSize.MEDIUM
: ec2.InstanceSize.MICRO
),
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
securityGroups: [dbSecurityGroup],
credentials: rds.Credentials.fromSecret(dbSecret),
multiAz: props.environment === 'prod',
allocatedStorage: 20,
maxAllocatedStorage: 100,
deletionProtection: props.environment === 'prod',
removalPolicy: props.environment === 'prod'
? cdk.RemovalPolicy.RETAIN
: cdk.RemovalPolicy.DESTROY
})
// ECS Cluster
const cluster = new ecs.Cluster(this, 'Cluster', {
vpc,
containerInsights: true,
enableFargateCapacityProviders: true
})
// ECR Repository
const repository = new ecr.Repository(this, 'Repository', {
repositoryName: `${id.toLowerCase()}-api`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
imageScanOnPush: true,
lifecycleRules: [
{
maxImageCount: 10,
rulePriority: 1
}
]
})
// Task Definition
const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
memoryLimitMiB: props.environment === 'prod' ? 2048 : 512,
cpu: props.environment === 'prod' ? 1024 : 256
})
const container = taskDefinition.addContainer('api', {
image: ecs.ContainerImage.fromEcrRepository(repository, 'latest'),
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'api',
logRetention: logs.RetentionDays.ONE_MONTH
}),
environment: {
NODE_ENV: props.environment,
PORT: '3000'
},
secrets: {
DATABASE_URL: ecs.Secret.fromSecretsManager(database.secret!),
DB_HOST: ecs.Secret.fromSecretsManager(database.secret!, 'host'),
DB_PASSWORD: ecs.Secret.fromSecretsManager(database.secret!, 'password')
},
healthCheck: {
command: ['CMD-SHELL', 'curl -f http://localhost:3000/health || exit 1'],
interval: cdk.Duration.seconds(30),
timeout: cdk.Duration.seconds(5),
retries: 3
}
})
container.addPortMappings({ containerPort: 3000 })
// Fargate Service
this.service = new ecs.FargateService(this, 'Service', {
cluster,
taskDefinition,
desiredCount: props.environment === 'prod' ? 3 : 1,
securityGroups: [ecsSecurityGroup],
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
capacityProviderStrategies: [
{
capacityProvider: 'FARGATE_SPOT',
weight: props.environment === 'prod' ? 1 : 2
},
{
capacityProvider: 'FARGATE',
weight: props.environment === 'prod' ? 2 : 1
}
],
circuitBreaker: { rollback: true },
enableExecuteCommand: true
})
// Auto Scaling
const scaling = this.service.autoScaleTaskCount({
minCapacity: props.environment === 'prod' ? 3 : 1,
maxCapacity: props.environment === 'prod' ? 20 : 5
})
scaling.scaleOnCpuUtilization('CpuScaling', {
targetUtilizationPercent: 70,
scaleInCooldown: cdk.Duration.seconds(60),
scaleOutCooldown: cdk.Duration.seconds(60)
})
scaling.scaleOnMemoryUtilization('MemoryScaling', {
targetUtilizationPercent: 70
})
// Application Load Balancer
this.loadBalancer = new elbv2.ApplicationLoadBalancer(this, 'Alb', {
vpc,
internetFacing: true,
securityGroup: albSecurityGroup
})
const listener = this.loadBalancer.addListener('HttpsListener', {
port: 443,
certificates: props.domainName
? [/* Add certificate */]
: undefined,
protocol: props.domainName
? elbv2.ApplicationProtocol.HTTPS
: elbv2.ApplicationProtocol.HTTP
})
listener.addTargets('EcsTarget', {
port: 3000,
targets: [this.service],
healthCheck: {
path: '/health',
interval: cdk.Duration.seconds(30),
healthyThresholdCount: 2,
unhealthyThresholdCount: 3
},
deregistrationDelay: cdk.Duration.seconds(30)
})
// HTTP to HTTPS redirect
if (props.domainName) {
this.loadBalancer.addListener('HttpListener', {
port: 80,
defaultAction: elbv2.ListenerAction.redirect({
port: '443',
protocol: 'HTTPS',
permanent: true
})
})
}
// Outputs
new cdk.CfnOutput(this, 'LoadBalancerDns', {
value: this.loadBalancer.loadBalancerDnsName
})
new cdk.CfnOutput(this, 'RepositoryUri', {
value: repository.repositoryUri
})
}
}Security scanning
Automatic code scanning
// Amazon Q automatically detects security issues
// ⚠️ Amazon Q warns:
// SQL Injection vulnerability detected
const query = `SELECT * FROM users WHERE id = '${userId}'`
// ✅ Amazon Q suggests a fix:
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb'
const command = new GetCommand({
TableName: 'users',
Key: { id: userId }
})
const result = await docClient.send(command)Types of detected issues
┌─────────────────────────────────────────────────────────┐
│ AMAZON Q SECURITY SCANNING │
├─────────────────────────────────────────────────────────┤
│ │
│ ⚠️ Injection Vulnerabilities │
│ └─ SQL, NoSQL, Command, LDAP injection │
│ │
│ ⚠️ Authentication Issues │
│ └─ Hardcoded credentials, weak passwords │
│ │
│ ⚠️ Sensitive Data Exposure │
│ └─ API keys, secrets in code │
│ │
│ ⚠️ Security Misconfiguration │
│ └─ Open S3 buckets, public resources │
│ │
│ ⚠️ Cross-Site Scripting (XSS) │
│ └─ Unescaped user input │
│ │
│ ⚠️ Insecure Dependencies │
│ └─ Known CVEs in packages │
│ │
└─────────────────────────────────────────────────────────┘Scanning in CI/CD
# .github/workflows/security-scan.yml
name: Security Scan
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
amazon-q-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: eu-central-1
- name: Run Amazon Q Security Scan
run: |
pip install amazon-q-developer-cli
amazon-q scan --format sarif --output results.sarif
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: results.sarif
- name: Fail on high severity
run: |
HIGH_COUNT=$(jq '.runs[0].results | map(select(.level == "error")) | length' results.sarif)
if [ "$HIGH_COUNT" -gt 0 ]; then
echo "Found $HIGH_COUNT high severity issues"
exit 1
fiCode transformation
Java modernization
// Amazon Q can automatically modernize Java code
// BEFORE (Java 8):
public class UserService {
private List<User> users = new ArrayList<>();
public List<User> findActiveUsers() {
List<User> result = new ArrayList<>();
for (User user : users) {
if (user.isActive()) {
result.add(user);
}
}
return result;
}
public Optional<User> findByEmail(String email) {
for (User user : users) {
if (user.getEmail().equals(email)) {
return Optional.of(user);
}
}
return Optional.empty();
}
}
// AFTER (Java 17) - Amazon Q transformation:
public class UserService {
private final List<User> users = new ArrayList<>();
public List<User> findActiveUsers() {
return users.stream()
.filter(User::isActive)
.toList();
}
public Optional<User> findByEmail(String email) {
return users.stream()
.filter(user -> user.getEmail().equals(email))
.findFirst();
}
}.NET modernization
// Amazon Q modernizes .NET Framework to .NET 8
// BEFORE (.NET Framework 4.8):
public class DataService
{
public async Task<List<Customer>> GetCustomersAsync()
{
using (var client = new HttpClient())
{
var response = await client.GetAsync("https://api.example.com/customers");
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<Customer>>(content);
}
}
}
// AFTER (.NET 8) - Amazon Q transformation:
public class DataService(HttpClient httpClient)
{
public async Task<List<Customer>> GetCustomersAsync()
{
return await httpClient.GetFromJsonAsync<List<Customer>>(
"https://api.example.com/customers"
) ?? [];
}
}Amazon Q in the AWS Console
Chat in the Console
User: How do I set up a VPC with public and private subnets?
Amazon Q:
I'll help you create a VPC with public and private subnets. Here's what you need:
1. **VPC**: Create a VPC with CIDR block (e.g., 10.0.0.0/16)
2. **Subnets**:
- Public: 10.0.1.0/24 (for ALB, NAT Gateway)
- Private: 10.0.2.0/24 (for EC2, Lambda)
3. **Internet Gateway**: Attach to VPC for public internet access
4. **NAT Gateway**: Place in public subnet for private subnet outbound access
5. **Route Tables**:
- Public: Route 0.0.0.0/0 → Internet Gateway
- Private: Route 0.0.0.0/0 → NAT Gateway
Would you like me to create a CloudFormation template for this setup?Troubleshooting with Amazon Q
User: My Lambda function is timing out when connecting to RDS
Amazon Q:
Based on the symptoms, here are the most common causes and solutions:
**1. VPC Configuration Issue (Most Common)**
- Lambda needs to be in the same VPC as RDS
- Check Security Groups allow traffic on port 5432/3306
- Ensure Lambda has a route to RDS subnet
**2. Security Group Configuration**RDS Security Group should allow:
Inbound: Port 5432 from Lambda Security Group
**3. NAT Gateway for Internet Access**
If Lambda needs both RDS and internet access, ensure:
- Lambda is in private subnet
- NAT Gateway is in public subnet
- Route table has 0.0.0.0/0 → NAT Gateway
**4. Connection Pool Exhaustion**
```python
# Use connection pooling
from sqlalchemy import create_engine
engine = create_engine(DATABASE_URL, pool_size=5, max_overflow=10)5. Increase Timeout
# SAM template
Timeout: 30 # Increase from default 3 secondsWould you like me to help diagnose which specific issue you're facing?
## Pricing
### Free Tier
**Price**: $0/month
**Includes:**
- 50 security scans/month
- Basic code suggestions
- VS Code and JetBrains integration
- Access to Amazon Q Chat
- AWS Console integration
### Pro Tier
**Price**: $19/user/month
**Includes:**
- Everything from Free
- Unlimited security scans
- Code transformation
- Administrative controls
- Higher quotas
- Priority support
### Enterprise
**Price**: Custom
**Includes:**
- Everything from Pro
- SSO/SAML
- Customization options
- Dedicated support
- SLA guarantees
- Compliance reporting
## Best practices
### Effective prompts
```typescript
// ❌ Too generic
// "Create Lambda function"
// ✅ Specific
// "Create Lambda function that:
// - Processes S3 events for image uploads
// - Resizes images to 800x600
// - Stores thumbnails in a separate bucket
// - Uses Sharp library for image processing
// - Includes error handling and CloudWatch logging"Project configuration
// .amazonq/config.json
{
"codeGeneration": {
"preferredLanguage": "typescript",
"preferredRuntime": "nodejs18.x",
"preferredRegion": "eu-central-1"
},
"securityScanning": {
"enabled": true,
"severity": "HIGH",
"autoFix": false
},
"infraAsCode": {
"preferredTool": "cdk",
"language": "typescript"
}
}FAQ
Is Amazon Q free?
Yes, Amazon Q has a free tier with 50 security scans per month and basic code suggestions. The Pro tier costs $19/user/month.
Does Amazon Q only work with AWS?
Amazon Q is optimized for AWS, but it can generate general-purpose code in many languages. However, its greatest value lies in its deep integration with the AWS ecosystem.
How does Amazon Q differ from Copilot?
Amazon Q specializes in AWS - it knows all the services, best practices, and security patterns. Copilot is more general-purpose, but it does not have such deep knowledge of AWS.
Can I use Amazon Q offline?
No, Amazon Q requires an internet connection to generate suggestions and perform security scanning.
Summary
Amazon Q Developer is an essential tool for any developer working with AWS. Thanks to its deep knowledge of AWS services, built-in security scanning, and code transformation capabilities, Amazon Q significantly accelerates cloud-native application development.
Key advantages:
- Deep AWS integration - Knows all 200+ services
- Security-first - Automatic vulnerability detection
- IaC generation - CloudFormation, CDK, Terraform
- Code modernization - Java, .NET transformation
- Free tier - Available for everyone
If you are building on AWS, Amazon Q should be part of your toolkit.