AWS Amplify
Platform
AWS Amplify
Overview
AWS Amplify is a comprehensive full-stack development platform provided by Amazon Web Services, supporting the building, deployment, and management of mobile and web applications holistically. The Gen 2 architecture adopts a TypeScript-first, code-first approach, evolving from traditional CLI-based configuration to infrastructure definition through code. It integrates authentication (Cognito), database (DynamoDB + GraphQL), storage (S3), CI/CD, hosting, and more, providing enterprise-level scalability and reliability through deep integration with the AWS ecosystem. This modern platform empowers frontend developers to build scalable applications using TypeScript and AWS CDK, offering features like real-time APIs, authentication, and storage.
Details
AWS Amplify 2025 edition realizes modern full-stack development by integrating TypeScript-first development experience with AWS CDK-based infrastructure. Gen 2 defines infrastructure in the amplify/backend.ts file, which is automatically deployed as an AWS CDK stack. It provides a unified DX for utilizing major AWS services including GraphQL API and DynamoDB, Amazon Cognito authentication, S3 storage, Lambda functions, and CloudFront CDN. Features include real-time data synchronization, offline support, and multi-platform SDKs (JavaScript, TypeScript, Flutter, Swift, Kotlin) to support scalable and secure application development. Additionally, it provides per-developer sandbox for parallel development environments and integration with AI/ML services (Bedrock, Translate, Polly, Rekognition). The code-first developer experience avoids CLI tool friction and enables AI assistance from services like Amazon Q Developer, making it easier for developers to build and deploy cloud infrastructure.
Key Features
- TypeScript-first DX: Infrastructure definition through code with type-safe development
- GraphQL + DynamoDB: Real-time APIs and NoSQL database
- Amazon Cognito: Comprehensive authentication and authorization system
- S3 + CloudFront: Scalable storage and CDN
- Lambda Functions: Serverless function execution environment
- CI/CD Pipeline: AWS-native deployment and automation
Pros and Cons
Pros
- Enterprise-level scalability through deep integration with AWS ecosystem
- Improved type safety and development efficiency with TypeScript-first approach
- Unified infrastructure and application management through code-first DX
- Efficient parallel development and CI/CD through per-developer sandbox
- Automatic GraphQL API generation and real-time data synchronization
- Rich integration with AWS services (AI/ML, analytics, security, etc.)
Cons
- Vendor lock-in and platform-specific learning costs due to AWS dependency
- Complex pricing structure and unpredictable costs at large scale
- Migration difficulty from existing RDBMS due to GraphQL/NoSQL-centric design
- DynamoDB limitations for complex queries and aggregation processing
- Configuration complexity and choice overload due to AWS service richness
- Multi-cloud strategy constraints with other cloud providers
Reference Pages
Code Examples
Basic Setup and Configuration
# Install Amplify CLI
npm install -g @aws-amplify/cli
# Create new Amplify project
npx create-amplify@latest
cd my-amplify-app
# Configure AWS account
ampx configure
# Start development server
npm run dev
# Deploy to production
npx ampx pipeline deploy --branch main
// amplify/backend.ts - Backend definition
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';
import { storage } from './storage/resource';
// Define backend resources
export const backend = defineBackend({
auth,
data,
storage,
});
// Add custom resources (CDK)
backend.addOutput({
custom: {
API_ENDPOINT: backend.data.resources.graphqlApi.graphqlUrl,
REGION: backend.data.resources.graphqlApi.region,
},
});
console.log('Backend configured successfully');
// src/main.ts - Frontend initialization
import { Amplify } from 'aws-amplify';
import outputs from '../amplify_outputs.json';
// Configure Amplify
Amplify.configure(outputs);
console.log('Amplify configured with:', {
region: outputs.auth?.aws_region,
userPoolId: outputs.auth?.user_pool_id,
apiEndpoint: outputs.data?.url,
});
// Start application
import './app';
GraphQL API and DataStore
// amplify/data/resource.ts - Data model definition
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
const schema = a.schema({
User: a
.model({
id: a.id().required(),
name: a.string().required(),
email: a.email().required(),
age: a.integer(),
posts: a.hasMany('Post', 'authorId'),
profile: a.hasOne('Profile', 'userId'),
createdAt: a.datetime().default(() => new Date().toISOString()),
isActive: a.boolean().default(true),
})
.authorization((allow) => [
allow.owner(),
allow.authenticated().to(['read']),
]),
Post: a
.model({
id: a.id().required(),
title: a.string().required(),
content: a.string().required(),
authorId: a.id().required(),
author: a.belongsTo('User', 'authorId'),
tags: a.string().array(),
status: a.enum(['DRAFT', 'PUBLISHED', 'ARCHIVED']),
publishedAt: a.datetime(),
viewCount: a.integer().default(0),
})
.authorization((allow) => [
allow.owner(),
allow.authenticated().to(['read']),
allow.guest().to(['read']).where((post) => post.status.eq('PUBLISHED')),
]),
Profile: a
.model({
id: a.id().required(),
userId: a.id().required(),
bio: a.string(),
avatar: a.string(),
website: a.url(),
location: a.string(),
user: a.belongsTo('User', 'userId'),
})
.authorization((allow) => [
allow.owner(),
allow.authenticated().to(['read']),
]),
});
export type Schema = ClientSchema<typeof schema>;
// Define data resource
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'userPool',
apiKeyAuthorizationMode: {
expiresInDays: 30,
},
},
});
// src/lib/api.ts - GraphQL client operations
import { generateClient } from 'aws-amplify/data';
import type { Schema } from '../../amplify/data/resource';
// Generate type-safe client
const client = generateClient<Schema>();
// Create user
export async function createUser(userData: {
name: string;
email: string;
age?: number;
}) {
try {
const { data: newUser, errors } = await client.models.User.create({
...userData,
isActive: true,
});
if (errors) {
console.error('User creation errors:', errors);
throw new Error('Failed to create user');
}
console.log('User created:', newUser);
return newUser;
} catch (error) {
console.error('Create user error:', error);
throw error;
}
}
// Subscribe to users (real-time)
export function subscribeToUsers() {
const subscription = client.models.User.observeQuery().subscribe({
next: (snapshot) => {
console.log('Users updated:', snapshot.items);
updateUsersList(snapshot.items);
},
error: (error) => {
console.error('User subscription error:', error);
},
});
return subscription;
}
// Create post
export async function createPost(postData: {
title: string;
content: string;
tags?: string[];
status?: 'DRAFT' | 'PUBLISHED';
}) {
try {
const { data: newPost, errors } = await client.models.Post.create({
...postData,
status: postData.status || 'DRAFT',
publishedAt: postData.status === 'PUBLISHED'
? new Date().toISOString()
: undefined,
});
if (errors) {
console.error('Post creation errors:', errors);
throw new Error('Failed to create post');
}
console.log('Post created:', newPost);
return newPost;
} catch (error) {
console.error('Create post error:', error);
throw error;
}
}
// Complex query execution
export async function getPublishedPostsByAuthor(authorId: string) {
try {
const { data: posts, errors } = await client.models.Post.list({
filter: {
authorId: { eq: authorId },
status: { eq: 'PUBLISHED' },
},
limit: 20,
});
if (errors) {
console.error('Query errors:', errors);
return [];
}
console.log('Published posts:', posts);
return posts;
} catch (error) {
console.error('Query error:', error);
throw error;
}
}
// Get relationships
export async function getUserWithPosts(userId: string) {
try {
const { data: user, errors } = await client.models.User.get(
{ id: userId },
{
selectionSet: [
'id',
'name',
'email',
'posts.id',
'posts.title',
'posts.status',
'posts.publishedAt',
'profile.bio',
'profile.avatar',
],
}
);
if (errors) {
console.error('User query errors:', errors);
return null;
}
console.log('User with posts:', user);
return user;
} catch (error) {
console.error('User query error:', error);
throw error;
}
}
Authentication with Cognito
// amplify/auth/resource.ts - Authentication configuration
import { defineAuth } from '@aws-amplify/backend';
export const auth = defineAuth({
loginWith: {
email: {
verificationEmailStyle: 'CODE',
verificationEmailSubject: 'Verify your email',
verificationEmailBody: (createCode) =>
`Your verification code is ${createCode()}`,
},
externalProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scopes: ['email', 'profile', 'openid'],
},
facebook: {
clientId: process.env.FACEBOOK_APP_ID!,
clientSecret: process.env.FACEBOOK_APP_SECRET!,
},
signInWithApple: {
clientId: process.env.APPLE_CLIENT_ID!,
keyId: process.env.APPLE_KEY_ID!,
privateKey: process.env.APPLE_PRIVATE_KEY!,
teamId: process.env.APPLE_TEAM_ID!,
},
callbackUrls: [
'http://localhost:3000/auth/callback',
'https://myapp.com/auth/callback',
],
logoutUrls: [
'http://localhost:3000/',
'https://myapp.com/',
],
},
},
userAttributes: {
email: {
required: true,
mutable: true,
},
givenName: {
required: true,
mutable: true,
},
familyName: {
required: true,
mutable: true,
},
phoneNumber: {
required: false,
mutable: true,
},
},
passwordPolicy: {
minLength: 8,
requireNumbers: true,
requireLowercase: true,
requireUppercase: true,
requireSymbols: true,
},
multifactor: {
mode: 'OPTIONAL',
sms: true,
totp: true,
},
});
// src/lib/auth.ts - Authentication operations
import {
signUp,
signIn,
signOut,
confirmSignUp,
resendSignUpCode,
signInWithRedirect,
getCurrentUser,
fetchAuthSession,
updateUserAttributes,
changePassword,
resetPassword,
confirmResetPassword,
deleteUser,
} from 'aws-amplify/auth';
// User registration
export async function registerUser({
email,
password,
givenName,
familyName,
}: {
email: string;
password: string;
givenName: string;
familyName: string;
}) {
try {
const { isSignUpComplete, userId, nextStep } = await signUp({
username: email,
password,
options: {
userAttributes: {
email,
given_name: givenName,
family_name: familyName,
},
},
});
console.log('Sign up result:', {
isSignUpComplete,
userId,
nextStep,
});
return { isSignUpComplete, userId, nextStep };
} catch (error) {
console.error('Sign up error:', error);
throw error;
}
}
// Confirm verification code
export async function confirmSignUpCode({
username,
confirmationCode,
}: {
username: string;
confirmationCode: string;
}) {
try {
const { isSignUpComplete, nextStep } = await confirmSignUp({
username,
confirmationCode,
});
console.log('Confirmation result:', {
isSignUpComplete,
nextStep,
});
return { isSignUpComplete, nextStep };
} catch (error) {
console.error('Confirmation error:', error);
throw error;
}
}
// User login
export async function loginUser({
username,
password,
}: {
username: string;
password: string;
}) {
try {
const { isSignedIn, nextStep } = await signIn({
username,
password,
});
console.log('Sign in result:', {
isSignedIn,
nextStep,
});
if (isSignedIn) {
const user = await getCurrentUser();
console.log('Current user:', user);
return { user, isSignedIn, nextStep };
}
return { user: null, isSignedIn, nextStep };
} catch (error) {
console.error('Sign in error:', error);
throw error;
}
}
// Social login
export async function signInWithGoogle() {
try {
await signInWithRedirect({
provider: 'Google',
customState: JSON.stringify({ returnUrl: window.location.pathname }),
});
} catch (error) {
console.error('Google sign in error:', error);
throw error;
}
}
// Get current user info
export async function getCurrentUserInfo() {
try {
const user = await getCurrentUser();
const session = await fetchAuthSession();
console.log('Current user info:', {
user,
tokens: session.tokens,
credentials: session.credentials,
});
return {
user,
session,
isAuthenticated: !!session.tokens,
};
} catch (error) {
console.error('Get current user error:', error);
return {
user: null,
session: null,
isAuthenticated: false,
};
}
}
// Update profile
export async function updateProfile(attributes: {
given_name?: string;
family_name?: string;
phone_number?: string;
}) {
try {
const result = await updateUserAttributes({
userAttributes: attributes,
});
console.log('Profile update result:', result);
return result;
} catch (error) {
console.error('Profile update error:', error);
throw error;
}
}
// Change password
export async function updatePassword({
oldPassword,
newPassword,
}: {
oldPassword: string;
newPassword: string;
}) {
try {
await changePassword({
oldPassword,
newPassword,
});
console.log('Password changed successfully');
} catch (error) {
console.error('Password change error:', error);
throw error;
}
}
// Logout
export async function logoutUser() {
try {
await signOut({ global: true });
console.log('Signed out successfully');
} catch (error) {
console.error('Sign out error:', error);
throw error;
}
}
Storage and CDN Integration
// amplify/storage/resource.ts - Storage configuration
import { defineStorage } from '@aws-amplify/backend';
export const storage = defineStorage({
name: 'myAppStorage',
access: (allow) => ({
'public/*': [
allow.guest.to(['read']),
allow.authenticated.to(['read', 'write', 'delete']),
],
'protected/{entity_id}/*': [
allow.authenticated.to(['read']),
allow.entity('identity').to(['read', 'write', 'delete']),
],
'private/{entity_id}/*': [
allow.entity('identity').to(['read', 'write', 'delete']),
],
'uploads/images/*': [
allow.authenticated.to(['read', 'write']),
],
'uploads/documents/*': [
allow.authenticated.to(['read', 'write']),
// File size limit (10MB)
allow.authenticated.to(['write']).when((context) =>
context.request.object.size <= 10 * 1024 * 1024
),
],
}),
});
// src/lib/storage.ts - Storage operations
import {
uploadData,
downloadData,
getUrl,
list,
remove,
getProperties,
} from 'aws-amplify/storage';
// File upload with progress
export async function uploadFile({
file,
path,
level = 'protected',
onProgress,
}: {
file: File;
path: string;
level?: 'public' | 'protected' | 'private';
onProgress?: (progress: { transferredBytes: number; totalBytes?: number }) => void;
}) {
try {
const key = level === 'public'
? `public/${path}`
: level === 'protected'
? `protected/{identity_id}/${path}`
: `private/{identity_id}/${path}`;
const uploadTask = uploadData({
path: key,
data: file,
options: {
contentType: file.type,
metadata: {
originalName: file.name,
uploadedAt: new Date().toISOString(),
},
onProgress: ({ transferredBytes, totalBytes }) => {
const progress = totalBytes ? (transferredBytes / totalBytes) * 100 : 0;
console.log(`Upload progress: ${progress.toFixed(2)}%`);
if (onProgress) {
onProgress({ transferredBytes, totalBytes });
}
},
},
});
const result = await uploadTask.result;
console.log('File uploaded successfully:', {
path: result.path,
size: file.size,
type: file.type,
});
return result;
} catch (error) {
console.error('Upload error:', error);
throw error;
}
}
// Image upload with thumbnail generation
export async function uploadImage({
file,
generateThumbnail = true,
}: {
file: File;
generateThumbnail?: boolean;
}) {
try {
// Image validation
if (!file.type.startsWith('image/')) {
throw new Error('Selected file is not an image');
}
if (file.size > 5 * 1024 * 1024) { // 5MB limit
throw new Error('Image size must be less than 5MB');
}
const fileName = `${Date.now()}_${file.name}`;
const imagePath = `uploads/images/${fileName}`;
// Upload original image
const uploadResult = await uploadFile({
file,
path: imagePath,
level: 'protected',
});
let thumbnailResult;
if (generateThumbnail) {
// Generate thumbnail (using Canvas API)
const thumbnailFile = await createThumbnail(file, 200, 200);
const thumbnailPath = `uploads/images/thumbnails/${fileName}`;
thumbnailResult = await uploadFile({
file: thumbnailFile,
path: thumbnailPath,
level: 'protected',
});
}
return {
original: uploadResult,
thumbnail: thumbnailResult,
};
} catch (error) {
console.error('Image upload error:', error);
throw error;
}
}
// Thumbnail generation function
function createThumbnail(
file: File,
maxWidth: number,
maxHeight: number
): Promise<File> {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
// Maintain aspect ratio and resize
const { width, height } = calculateDimensions(
img.width,
img.height,
maxWidth,
maxHeight
);
canvas.width = width;
canvas.height = height;
ctx?.drawImage(img, 0, 0, width, height);
canvas.toBlob(
(blob) => {
if (blob) {
const thumbnailFile = new File(
[blob],
`thumb_${file.name}`,
{ type: file.type }
);
resolve(thumbnailFile);
} else {
reject(new Error('Failed to create thumbnail'));
}
},
file.type,
0.8
);
};
img.onerror = () => reject(new Error('Failed to load image'));
img.src = URL.createObjectURL(file);
});
}
function calculateDimensions(
originalWidth: number,
originalHeight: number,
maxWidth: number,
maxHeight: number
) {
const ratio = Math.min(maxWidth / originalWidth, maxHeight / originalHeight);
return {
width: Math.round(originalWidth * ratio),
height: Math.round(originalHeight * ratio),
};
}
// List files
export async function listFiles({
path = '',
level = 'protected',
pageSize = 100,
}: {
path?: string;
level?: 'public' | 'protected' | 'private';
pageSize?: number;
} = {}) {
try {
const prefix = level === 'public'
? `public/${path}`
: level === 'protected'
? `protected/{identity_id}/${path}`
: `private/{identity_id}/${path}`;
const result = await list({
path: prefix,
options: {
listAll: true,
pageSize,
},
});
console.log('Files listed:', result.items.length);
return result.items;
} catch (error) {
console.error('List files error:', error);
throw error;
}
}
// File download
export async function downloadFile({
path,
level = 'protected',
}: {
path: string;
level?: 'public' | 'protected' | 'private';
}) {
try {
const key = level === 'public'
? `public/${path}`
: level === 'protected'
? `protected/{identity_id}/${path}`
: `private/{identity_id}/${path}`;
const downloadResult = await downloadData({
path: key,
});
const blob = await downloadResult.result.body.blob();
console.log('File downloaded:', {
path,
size: blob.size,
type: blob.type,
});
return blob;
} catch (error) {
console.error('Download error:', error);
throw error;
}
}
// Get signed URL
export async function getSignedUrl({
path,
level = 'protected',
expiresIn = 3600, // 1 hour
}: {
path: string;
level?: 'public' | 'protected' | 'private';
expiresIn?: number;
}) {
try {
const key = level === 'public'
? `public/${path}`
: level === 'protected'
? `protected/{identity_id}/${path}`
: `private/{identity_id}/${path}`;
const signedUrl = await getUrl({
path: key,
options: {
expiresIn,
},
});
console.log('Signed URL generated:', signedUrl.url.toString());
return signedUrl.url;
} catch (error) {
console.error('Get signed URL error:', error);
throw error;
}
}
Serverless Functions and APIs
// amplify/functions/process-image/resource.ts - Lambda function definition
import { defineFunction } from '@aws-amplify/backend';
export const processImage = defineFunction({
name: 'processImage',
entry: './handler.ts',
environment: {
STORAGE_BUCKET_NAME: 'my-app-storage-bucket',
},
runtime: 20,
timeoutSeconds: 30,
memoryMB: 512,
});
// amplify/functions/process-image/handler.ts - Lambda function implementation
import type { S3Handler } from 'aws-lambda';
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import sharp from 'sharp';
const s3Client = new S3Client({ region: process.env.AWS_REGION });
export const handler: S3Handler = async (event) => {
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, ' '));
// Process only image files
if (!key.match(/\.(jpg|jpeg|png|gif|bmp|webp)$/i)) {
console.log('Skipping non-image file:', key);
continue;
}
try {
// Get original image
const getObjectResponse = await s3Client.send(
new GetObjectCommand({
Bucket: bucket,
Key: key,
})
);
if (!getObjectResponse.Body) {
console.error('Failed to get object body for:', key);
continue;
}
const imageBuffer = await streamToBuffer(getObjectResponse.Body);
// Generate multiple thumbnail sizes
const thumbnailSizes = [
{ suffix: 'thumb_sm', width: 150, height: 150 },
{ suffix: 'thumb_md', width: 300, height: 300 },
{ suffix: 'thumb_lg', width: 600, height: 600 },
];
for (const size of thumbnailSizes) {
const thumbnailBuffer = await sharp(imageBuffer)
.resize(size.width, size.height, {
fit: 'cover',
position: 'center',
})
.jpeg({ quality: 85 })
.toBuffer();
const thumbnailKey = key.replace(
/(.+)(\.[^.]+)$/,
`$1_${size.suffix}$2`
);
await s3Client.send(
new PutObjectCommand({
Bucket: bucket,
Key: thumbnailKey,
Body: thumbnailBuffer,
ContentType: 'image/jpeg',
Metadata: {
originalKey: key,
thumbnailSize: `${size.width}x${size.height}`,
processedAt: new Date().toISOString(),
},
})
);
console.log('Thumbnail created:', thumbnailKey);
}
// Convert to WebP format
const webpBuffer = await sharp(imageBuffer)
.webp({ quality: 85 })
.toBuffer();
const webpKey = key.replace(/\.[^.]+$/, '.webp');
await s3Client.send(
new PutObjectCommand({
Bucket: bucket,
Key: webpKey,
Body: webpBuffer,
ContentType: 'image/webp',
Metadata: {
originalKey: key,
format: 'webp',
processedAt: new Date().toISOString(),
},
})
);
console.log('WebP version created:', webpKey);
} catch (error) {
console.error('Error processing image:', key, error);
}
}
};
// Stream to Buffer utility
async function streamToBuffer(stream: any): Promise<Buffer> {
const chunks: Uint8Array[] = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
return Buffer.concat(chunks);
}
// amplify/functions/api/resource.ts - REST API definition
import { defineFunction } from '@aws-amplify/backend';
export const api = defineFunction({
name: 'api',
entry: './handler.ts',
environment: {
DATA_TABLE_NAME: 'MyAppData',
},
});
// amplify/functions/api/handler.ts - REST API handler
import type { APIGatewayProxyHandler } from 'aws-lambda';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand, PutCommand, ScanCommand } from '@aws-sdk/lib-dynamodb';
const dynamoClient = new DynamoDBClient({ region: process.env.AWS_REGION });
const docClient = DynamoDBDocumentClient.from(dynamoClient);
export const handler: APIGatewayProxyHandler = async (event) => {
console.log('API Gateway event:', JSON.stringify(event, null, 2));
const { httpMethod, path, pathParameters, body, queryStringParameters } = event;
// CORS headers
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
};
try {
// OPTIONS request (CORS preflight)
if (httpMethod === 'OPTIONS') {
return {
statusCode: 200,
headers: corsHeaders,
body: '',
};
}
// Routing
if (path === '/api/health' && httpMethod === 'GET') {
return {
statusCode: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({
status: 'healthy',
timestamp: new Date().toISOString(),
version: '1.0.0',
}),
};
}
if (path === '/api/users' && httpMethod === 'GET') {
const result = await docClient.send(
new ScanCommand({
TableName: process.env.DATA_TABLE_NAME,
Limit: parseInt(queryStringParameters?.limit || '20'),
})
);
return {
statusCode: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({
users: result.Items,
count: result.Count,
scannedCount: result.ScannedCount,
}),
};
}
if (path?.startsWith('/api/users/') && httpMethod === 'GET') {
const userId = pathParameters?.proxy || pathParameters?.id;
if (!userId) {
return {
statusCode: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'User ID is required' }),
};
}
const result = await docClient.send(
new GetCommand({
TableName: process.env.DATA_TABLE_NAME,
Key: { id: userId },
})
);
if (!result.Item) {
return {
statusCode: 404,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'User not found' }),
};
}
return {
statusCode: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ user: result.Item }),
};
}
if (path === '/api/users' && httpMethod === 'POST') {
if (!body) {
return {
statusCode: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Request body is required' }),
};
}
const userData = JSON.parse(body);
const userId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const newUser = {
id: userId,
...userData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
await docClient.send(
new PutCommand({
TableName: process.env.DATA_TABLE_NAME,
Item: newUser,
})
);
return {
statusCode: 201,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ user: newUser }),
};
}
// 404 for unmatched routes
return {
statusCode: 404,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({ error: 'Route not found' }),
};
} catch (error) {
console.error('API error:', error);
return {
statusCode: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
body: JSON.stringify({
error: 'Internal server error',
message: error instanceof Error ? error.message : 'Unknown error',
}),
};
}
};
CI/CD and Production Deployment
# .github/workflows/deploy.yml - GitHub Actions CI/CD
name: Deploy to AWS Amplify
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
AWS_REGION: us-east-1
NODE_VERSION: 18
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run type checking
run: npm run type-check
- name: Run linting
run: npm run lint
- name: Run tests
run: npm run test:coverage
- name: Build application
run: npm run build
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
deploy-staging:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/develop'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- 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: ${{ env.AWS_REGION }}
- name: Install dependencies
run: npm ci
- name: Deploy to staging
run: |
npx ampx pipeline deploy \
--branch develop \
--app-id ${{ secrets.AMPLIFY_APP_ID_STAGING }}
deploy-production:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- 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: ${{ env.AWS_REGION }}
- name: Install dependencies
run: npm ci
- name: Deploy to production
run: |
npx ampx pipeline deploy \
--branch main \
--app-id ${{ secrets.AMPLIFY_APP_ID_PRODUCTION }}
- name: Run post-deployment tests
run: |
npm run test:e2e -- --baseUrl=${{ secrets.PRODUCTION_URL }}
- name: Notify deployment success
if: success()
uses: 8398a7/action-slack@v3
with:
status: success
text: "🚀 Production deployment successful!"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
# amplify/.env - Environment variables
# Development environment
AWS_REGION=us-east-1
NODE_ENV=development
DEBUG=true
# Social login
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
FACEBOOK_APP_ID=your_facebook_app_id
FACEBOOK_APP_SECRET=your_facebook_app_secret
APPLE_CLIENT_ID=your_apple_client_id
APPLE_KEY_ID=your_apple_key_id
APPLE_TEAM_ID=your_apple_team_id
APPLE_PRIVATE_KEY=your_apple_private_key
# External APIs
STRIPE_SECRET_KEY=sk_test_your_stripe_key
SENDGRID_API_KEY=your_sendgrid_api_key
# Monitoring and analytics
SENTRY_DSN=your_sentry_dsn
DATADOG_API_KEY=your_datadog_api_key
// package.json - Script configuration
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"type-check": "tsc --noEmit",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint src --ext ts,tsx --fix",
"test": "vitest",
"test:coverage": "vitest run --coverage",
"test:e2e": "playwright test",
"amplify:dev": "npx ampx sandbox",
"amplify:deploy": "npx ampx pipeline deploy",
"amplify:delete": "npx ampx sandbox delete",
"generate:api": "npx ampx generate graphql-client-code --format typescript",
"db:migrate": "npx ampx generate migrate",
"logs": "npx ampx logs",
"console": "npx ampx console"
},
"dependencies": {
"aws-amplify": "^6.0.0",
"@aws-amplify/ui-react": "^6.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@aws-amplify/backend": "^1.0.0",
"@aws-amplify/backend-cli": "^1.0.0",
"typescript": "^5.2.0",
"vite": "^5.0.0",
"vitest": "^1.0.0",
"@playwright/test": "^1.40.0"
}
}