Nine Essential Tips of AWS Amplify for Boosting Development Productivity
Overview
AWS Amplify is a powerful set of tools and services for developing, hosting, and managing serverless applications. With the recent launch of Amplify Gen 212, the platform has evolved significantly to enhance the developer experience. In this guide, we'll explore nine essential tips that will help you maximize your productivity with AWS Amplify, covering everything from authentication and infrastructure management to AI integration and deployment.
Understanding Amplify Gen 2
Before diving into the tips, let's understand what makes Amplify Gen 2 special. It introduces a code-first developer experience that enables building fullstack applications using TypeScript. Key benefits include:
- TypeScript-first backend development
- Faster local development with cloud sandbox environments
- Improved team workflows with fullstack Git branches
- Unified management console
- Enhanced integration with AWS CDK
Tip 1: Implementing Third-Party Authentication
AWS Amplify provides seamless integration with popular authentication providers like Google, Facebook, and Amazon. You can also leverage any service supporting industry-standard protocols like OpenID Connect (OIDC) or SAML. While the built-in Authenticator component doesn't directly support third-party provider customization, you can achieve this through Header and Footer customization.
1<Authenticator
2 components={{
3 Header: SignInHeader,
4 SignIn: {
5 Header() {
6 return (
7 <div className="px-8 py-2">
8 <Flex direction="column"
9 className="federated-sign-in-container">
10 <Button
11 onClick={async () => {
12 await signInWithRedirect({
13 provider: {
14 custom: 'OIDC-Provider' // OIDC Provider name created in Cognito User Pool
15 }
16 });
17 }}
18 className="federated-sign-in-button"
19 gap="1rem"
20 >
21 <svg
22 xmlns="http://www.w3.org/2000/svg"
23 fill="#000"
24 version="1.1"
25 viewBox="0 0 32 32"
26 xmlSpace="preserve"
27 className="amplify-icon federated-sign-in-icon"
28 >
29 <path
30 d="M31 31.36H1v-.72h30v.72zm0-7H1A.36.36 0 01.64 24V1A.36.36 0 011 .64h30a.36.36 0 01.36.36v23a.36.36 0 01-.36.36zm-29.64-.72h29.28V1.36H1.36v22.28zm7.304-7.476c-.672 0-1.234-.128-1.687-.385s-.842-.6-1.169-1.029l.798-.644c.28.355.593.628.938.819.345.191.747.287 1.204.287.476 0 .847-.103 1.113-.308.266-.206.399-.495.399-.868 0-.28-.091-.52-.273-.721-.182-.201-.511-.338-.987-.414l-.574-.084a4.741 4.741 0 01-.924-.217c-.28-.098-.525-.229-.735-.392s-.374-.366-.49-.609a1.983 1.983 0 01-.175-.868c0-.354.065-.665.196-.931.13-.266.31-.488.539-.665s.501-.311.819-.399a3.769 3.769 0 011.022-.133c.588 0 1.08.103 1.477.308.396.206.744.49 1.043.854l-.742.672c-.159-.224-.392-.427-.7-.609-.308-.182-.695-.272-1.162-.272s-.819.1-1.057.3c-.238.201-.357.474-.357.819 0 .354.119.611.357.77.238.159.581.275 1.029.35l.56.084c.803.122 1.372.353 1.708.693.336.341.504.786.504 1.337 0 .7-.238 1.251-.714 1.652-.476.402-1.13.603-1.96.603zm6.733 0c-.672 0-1.234-.128-1.687-.385s-.842-.6-1.169-1.029l.798-.644c.28.355.593.628.938.819.345.191.747.287 1.204.287.476 0 .847-.103 1.113-.308.266-.206.399-.495.399-.868 0-.28-.091-.52-.273-.721-.182-.201-.511-.338-.987-.413l-.574-.084c-.336-.046-.644-.119-.924-.217s-.525-.229-.735-.392-.374-.366-.49-.609a1.983 1.983 0 01-.175-.868c0-.354.065-.665.196-.931.13-.266.31-.488.539-.665.229-.177.501-.311.819-.399a3.769 3.769 0 011.022-.133c.588 0 1.08.103 1.477.308.396.206.744.49 1.043.854l-.742.672c-.158-.224-.392-.427-.7-.609s-.695-.273-1.162-.273-.819.101-1.057.301c-.238.201-.357.474-.357.819 0 .354.119.611.357.77s.581.275 1.029.35l.56.084c.803.122 1.372.353 1.708.693.337.341.505.786.505 1.337 0 .7-.238 1.251-.715 1.652-.475.401-1.129.602-1.96.602zm7.378 0c-.485 0-.929-.089-1.33-.266s-.744-.432-1.028-.763a3.584 3.584 0 01-.665-1.19 4.778 4.778 0 01-.238-1.561c0-.569.079-1.087.238-1.554a3.56 3.56 0 01.665-1.197c.284-.332.627-.586 1.028-.763s.845-.266 1.33-.266.927.089 1.323.266.739.432 1.029.763c.289.331.513.73.672 1.197.158.467.238.985.238 1.554 0 .579-.08 1.099-.238 1.561a3.546 3.546 0 01-.672 1.19c-.29.331-.633.585-1.029.763a3.19 3.19 0 01-1.323.266zm0-.995c.606 0 1.102-.187 1.484-.56.383-.373.574-.942.574-1.708v-1.036c0-.765-.191-1.334-.574-1.708s-.878-.56-1.484-.56-1.102.187-1.483.56c-.383.374-.574.943-.574 1.708v1.036c0 .766.191 1.335.574 1.708.382.374.877.56 1.483.56z"></path>
31 <path fill="none" d="M0 0H32V32H0z"></path>
32 </svg>
33 <span style={{color: "white !important"}}>Sign In with My OIDC Provider</span>
34 </Button>
35 <Divider label="or" size="small"/>
36 </Flex>
37 </div>
38 );
39 }
40 }
41 }}
42 loginMechanisms={['email']}
43 signUpAttributes={['email']}
44 initialState="signIn"
45 hideSignUp={true}
46/>
Tip 2: Building Passwordless Authentication
Amazon Cognito now supports passwordless authentication, including sign-in with passkeys, email, and text messages. While the Authenticator component doesn't natively support these features, you can create a custom authentication experience using the Amplify JS library.
1import { useState } from 'react';
2import { useRouter } from 'next/navigation';
3import { signIn, confirmSignIn, fetchUserAttributes } from 'aws-amplify/auth';
4import { TextField, Button, CircularProgress, Alert } from '@mui/material';
5
6export default function Home() {
7 const [email, setEmail] = useState('');
8 const [code, setCode] = useState('');
9 const [loading, setLoading] = useState(false);
10 const [error, setError] = useState('');
11 const [showConfirmation, setShowConfirmation] = useState(false);
12 const router = useRouter();
13
14 const handleSignIn = async (e: React.FormEvent) => {
15 e.preventDefault();
16 setLoading(true);
17 setError('');
18
19 try {
20 const { nextStep } = await signIn({
21 username: email,
22 options: {
23 authFlowType: 'USER_AUTH',
24 preferredChallenge: 'EMAIL_OTP',
25 },
26 });
27 if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE' ||
28 nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION'
29 ) {
30 setShowConfirmation(true);
31 }
32 } catch (err) {
33 setError(err instanceof Error ? err.message : 'Sign in failed');
34 } finally {
35 setLoading(false);
36 }
37 };
38
39 const handleConfirmSignIn = async (e: React.FormEvent) => {
40 e.preventDefault();
41 setLoading(true);
42 setError('');
43
44 try {
45 const { nextStep: confirmSignInNextStep } = await confirmSignIn({ challengeResponse: code });
46
47 if (confirmSignInNextStep.signInStep === 'DONE') {
48 const attributes = await fetchUserAttributes();
49 if (attributes.email) {
50 router.push('/home');
51 }
52 }
53 } catch (err) {
54 setError(err instanceof Error ? err.message : 'Confirmation failed');
55 } finally {
56 setLoading(false);
57 }
58 };
59
60 return (
61 <div className="flex items-center justify-center min-h-screen">
62 <div className="w-full max-w-md p-6">
63 <div className="text-center mb-8">
64 <h1 className="text-2xl font-bold mb-2">Sign in to My App</h1>
65 <p className="text-gray-600">
66 {showConfirmation ? 'Enter the code sent to your email' : 'Enter your email to receive a code'}
67 </p>
68 </div>
69
70 {error && (
71 <Alert severity="error" className="mb-4">
72 {error}
73 </Alert>
74 )}
75
76 {!showConfirmation ? (
77 <form onSubmit={handleSignIn}>
78 <TextField
79 fullWidth
80 label="Email"
81 type="email"
82 value={email}
83 onChange={(e) => setEmail(e.target.value)}
84 disabled={loading}
85 required
86 className="mb-4"
87 />
88 <Button
89 fullWidth
90 variant="contained"
91 type="submit"
92 disabled={loading}
93 className="mt-2"
94 >
95 {loading ? <CircularProgress size={24} /> : 'Continue'}
96 </Button>
97 </form>
98 ) : (
99 <form onSubmit={handleConfirmSignIn}>
100 <TextField
101 fullWidth
102 label="Verification Code"
103 value={code}
104 onChange={(e) => setCode(e.target.value)}
105 disabled={loading}
106 required
107 className="mb-4"
108 />
109 <Button
110 fullWidth
111 variant="contained"
112 type="submit"
113 disabled={loading}
114 className="mt-2"
115 >
116 {loading ? <CircularProgress size={24} /> : 'Verify'}
117 </Button>
118 </form>
119 )}
120 </div>
121 </div>
122 );
123}
Tip 3: Managing Backend Access with ID Tokens
When working with authenticated users, proper token management is crucial. While Amplify automatically handles access tokens for data API requests, some scenarios require manual token management for accessing user attributes in your backend services.
1import { fetchAuthSession } from 'aws-amplify/auth';
2
3const session = await fetchAuthSession();
4if (!session.tokens?.idToken) throw new Error('User not signed in');
5
6await client.mutations.action({
7 ...formData,
8}, {
9 authMode: 'userPool',
10 headers: {
11 'Authorization': session.tokens.idToken.toString(),
12 }
13});
In the backend, you can use the email attribute of the user like below if you are using AppSync JS resolver:
1import { util } from '@aws-appsync/utils';
2
3export function request(ctx) {
4 const owner = ctx.identity.claims.email || ctx.identity.username;
5}
6
7export function response(ctx) {
8 return ctx.result;
9}
Tip 4: Mastering UI Development
Amplify UI provides a rich set of components designed for seamless integration. Learn how to maintain a consistent look and feel when combining Amplify UI with other popular libraries like Material-UI (MUI).
1import { ThemeProvider, createTheme, defaultDarkModeOverride } from '@aws-amplify/ui-react';
2import { styled, ThemeProvider as MUIThemeProvider, createTheme } from '@mui/material/styles';
3
4const theme = createTheme({
5 name: 'christmas-theme',
6 tokens: {
7 colors: {
8 background: {
9 primary: { value: '#FFFFFF' }, // Snow white background
10 secondary: { value: '#165B33' }, // Christmas green
11 },
12 },
13 components: {
14 button: {
15 primary: {
16 backgroundColor: { value: '#CC231E' },
17 color: { value: '#FFFFFF' },
18 _hover: {
19 backgroundColor: { value: '#165B33' },
20 },
21 },
22 },
23 },
24 },
25 overrides: [defaultDarkModeOverride]
26});
27
28const muiTheme = createTheme({
29 palette: {
30 primary: {
31 main: theme.tokens.colors.font.interactive.value,
32 },
33 },
34});
35
36export default function RootLayout({
37 children,
38}: {
39 children: React.ReactNode;
40}) {
41 return (
42 <html lang="en" className={inter.className}>
43 <head>
44 <meta name="viewport" content="width=device-width, initial-scale=1" />
45 </head>
46 <body>
47 <ThemeProvider theme={theme}>
48 <AmplifyProvider>
49 <main className="min-h-screen">
50 <MUIThemeProvider theme={muiTheme}>
51 {children}
52 </MUIThemeProvider>
53 </main>
54 </AmplifyProvider>
55 </ThemeProvider>
56 </body>
57 </html>
58 );
59}
Tip 5: Extending Infrastructure with CDK
For complex backend requirements, AWS CDK enables powerful customization of your Amplify backend. This allows you to manage all types of AWS resources while benefiting from the extensive CDK construct ecosystem.
1(backend.leagueHandler.resources.lambda.node.defaultChild as CfnFunction).addPropertyOverride('LoggingConfig', {
2 LogFormat: 'JSON',
3 ApplicationLogLevel: process.env.PRODUCTION ? 'WARN' : 'TRACE',
4 SystemLogLevel: 'INFO',
5});
Tip 6: Optimizing DynamoDB Access
Learn how to handle common challenges like circular dependencies when accessing DynamoDB tables from Lambda resolvers in your Amplify-generated AppSync API. You can access user identity information in your resolvers using AppSync identity context.
1import { defineBackend } from '@aws-amplify/backend';
2import { auth } from './auth/resource';
3import { data, leagueHandler } from './data/resource';
4export const backend = defineBackend({
5 auth,
6 data,
7 leagueHandler,
8});
9
10const externalTableStack = backend.createStack('ExternalTableStack');
11
12const leagueTable = new Table(externalTableStack, 'League', {
13 partitionKey: {
14 name: 'id',
15 type: AttributeType.STRING
16 },
17 billingMode: BillingMode.PAY_PER_REQUEST,
18 removalPolicy: RemovalPolicy.DESTROY,
19});
20
21backend.data.addDynamoDbDataSource(
22 "ExternalLeagueTableDataSource",
23 leagueTable as any
24);
25
26leagueTable.grantReadWriteData(backend.leagueHandler.resources.lambda);
27(backend.leagueHandler.resources.lambda as NodejsFunction).addEnvironment('LEAGUE_TABLE_NAME', leagueTable.tableName);
amplify/backend.ts
1const schema = a.schema({
2 League: a.customType({
3 id: a.string().required(),
4 leagueCountry: a.ref('LeagueCountry'),
5 teams: a.ref('Team').array(),
6 season: a.integer(),
7 }),
8});
amplify/data/resource.ts
, see here for more details.Tip 7: Building Resilient AI Features
Improve your application's reliability by implementing cross-region model inference with the Amplify AI Kit. While not supported out-of-the-box, you can achieve this using CDK Interoperability.
Hack the role of Lambda function for conversation
and AppSync resolver role for generate
in amplify/backend.ts
1function createBedrockPolicyStatement(currentRegion: string, accountId: string, modelId: string, crossRegionModel: string) {
2 return new PolicyStatement({
3 resources: [
4 `arn:aws:bedrock:*::foundation-model/${modelId}`,
5 `arn:aws:bedrock:${currentRegion}:${accountId}:inference-profile/${crossRegionModel}`,
6 ],
7 actions: ['bedrock:InvokeModel*'],
8 });
9}
10
11if (CROSS_REGION_INFERENCE && CUSTOM_MODEL_ID) {
12 const currentRegion = getCurrentRegion(backend.stack);
13 const crossRegionModel = getCrossRegionModelId(currentRegion, CUSTOM_MODEL_ID);
14
15 // [chat converstation]
16 const chatStack = backend.data.resources.nestedStacks?.['ChatConversationDirectiveLambdaStack'];
17 if (chatStack) {
18 const conversationFunc = chatStack.node.findAll()
19 .find(child => child.node.id === 'conversationHandlerFunction') as IFunction;
20
21 if (conversationFunc) {
22 conversationFunc.addToRolePolicy(
23 createBedrockPolicyStatement(currentRegion, backend.stack.account, CUSTOM_MODEL_ID, crossRegionModel)
24 );
25 }
26 }
27
28 // [insights generation]
29 const insightsStack = backend.data.resources.nestedStacks?.['GenerationBedrockDataSourceGenerateInsightsStack'];
30 if (insightsStack) {
31 const dataSourceRole = insightsStack.node.findChild('GenerationBedrockDataSourceGenerateInsightsIAMRole') as IRole;
32 if (dataSourceRole) {
33 dataSourceRole.attachInlinePolicy(
34 new Policy(insightsStack, 'CrossRegionInferencePolicy', {
35 statements: [
36 createBedrockPolicyStatement(currentRegion, backend.stack.account, CUSTOM_MODEL_ID, crossRegionModel)
37 ],
38 }),
39 );
40 }
41 }
42}
Specify the model ID in amplify/data/resource.ts
1const schema = a.schema({
2 generateInsights: a.generation({
3 aiModel: CROSS_REGION_INFERENCE ? {
4 resourcePath: getCrossRegionModelId(getCurrentRegion(undefined), CUSTOM_MODEL_ID!),
5 } : a.ai.model(LLM_MODEL),
6 systemPrompt: LLM_SYSTEM_PROMPT,
7 inferenceConfiguration: {
8 maxTokens: 1000,
9 temperature: 0.65,
10 },
11 })
12 .arguments({
13 requirement: a.string().required(),
14 })
15 .returns(a.customType({
16 insights: a.string().required(),
17 }))
18 .authorization(allow => [allow.authenticated()]),
19
20 chat: a.conversation({
21 aiModel: CROSS_REGION_INFERENCE ? {
22 resourcePath: getCrossRegionModelId(getCurrentRegion(undefined), CUSTOM_MODEL_ID!),
23 } : a.ai.model(LLM_MODEL),
24 systemPrompt: FOOTBALL_SYSTEM_PROMPT,
25 }).authorization(allow => allow.owner()),
26});
Tip 8: Creating Sophisticated Chat Interfaces
The AIConversation component provides a flexible foundation for building chat applications. Master state management and user context handling for multiple conversations.
1import { useState } from 'react';
2import { Fab, Paper, IconButton, Box, Tooltip, Typography } from '@mui/material';
3import { AIConversation } from '@aws-amplify/ui-react-ai';
4import { Avatar } from '@aws-amplify/ui-react';
5import '@aws-amplify/ui-react/styles.css';
6import { generateClient } from 'aws-amplify/data';
7import { createAIHooks } from '@aws-amplify/ui-react-ai';
8import { type Schema } from '../../amplify/data/resource';
9import ReactMarkdown from 'react-markdown';
10
11const client = generateClient<Schema>({ authMode: 'userPool' });
12const { useAIConversation } = createAIHooks(client);
13
14interface ChatBotProps {
15 chatId?: string;
16 refreshKey: number;
17 onStartNewChat: () => void;
18 onLoadConversations: () => void;
19 isLoading: boolean;
20}
21
22export default function ChatBot({
23 chatId,
24 refreshKey,
25 onStartNewChat,
26 onLoadConversations,
27 isLoading
28}: ChatBotProps) {
29 const [open, setOpen] = useState(refreshKey > 0);
30 const [position, setPosition] = useState({ x: 0, y: 0 });
31
32 const conversation = useAIConversation('chat', {
33 id: chatId,
34 });
35 const [{ data: { messages }, isLoading: isLoadingChat }, sendMessage] = conversation;
36
37 const handleOpen = () => {
38 setOpen(true);
39 onLoadConversations();
40 };
41
42 const handleClose = () => setOpen(false);
43
44 const handleNewChat = () => {
45 // Reset conversation and create new chat
46 onStartNewChat();
47 };
48
49 return (
50<Box sx={{ flexGrow: 1, overflow: 'hidden' }}>
51 <AIConversation
52 key={chatId}
53 allowAttachments
54 messages={messages}
55 handleSendMessage={sendMessage}
56 isLoading={isLoadingChat || isLoading}
57 avatars={{
58 user: {
59 avatar: <Avatar size="small" alt={email} />,
60 username: 'People'
61 },
62 ai: {
63 avatar: <Avatar size="small" alt="AI" />,
64 username: 'Chat Bot'
65 }
66 }}
67 messageRenderer={{
68 text: ({ text }) => <ReactMarkdown>{text}</ReactMarkdown>,
69 }}
70 />
71</Box>
72 );
73}
Tip 9: Streamlining Deployment Debugging
When troubleshooting deployment issues in Amplify Hosting, leverage the --debug
flag for deeper insights into pipeline failures, especially when code works in sandbox but fails in production.
1version: 1
2backend:
3 phases:
4 build:
5 commands:
6 - nvm install 20
7 - nvm use 20
8 - npm ci --cache .npm --prefer-offline
9 - npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID --debug
10frontend:
11 phases:
12 preBuild:
13 commands:
14 - nvm install 20
15 - nvm use 20
16 build:
17 commands:
18 - npm run build
19 artifacts:
20 baseDirectory: .next
21 files:
22 - '**/*'
23 cache:
24 paths:
25 - .next/cache/**/*
26 - .npm/**/*
Conclusion
AWS Amplify Gen 2 represents a significant evolution in fullstack development on AWS, offering a developer experience comparable to platforms like Vercel with Next.js. These tips will help you leverage Amplify's generated services alongside CDK's powerful constructs to build sophisticated serverless applications efficiently. The platform's seamless integration with the AWS ecosystem makes it an excellent choice for teams looking to accelerate their development process while maintaining enterprise-grade quality and scalability.