Home DevOps AWS Amplify Full-Stack App Development: Build Fast Without Losing Control
Intermediate 3 min · July 18, 2026

AWS Amplify Full-Stack App Development: Build Fast Without Losing Control

AWS Amplify full-stack development guide: production patterns, gotchas, and when to ditch it.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
Production
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
Before you start⏱ 30 min
  • Basic knowledge of AWS services (Lambda, DynamoDB, S3)
  • Familiarity with React or a similar frontend framework
  • Comfort with CLI and Git workflows
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer

AWS Amplify lets you define your app's backend (auth, API, storage) in a single configuration file and deploy it with one command. It's great for prototyping and small teams, but for complex production systems, you'll hit limits on customization and debugging.

✦ Definition~90s read
What is AWS Amplify?

AWS Amplify is a set of tools and services that lets you build full-stack apps—frontend, backend, and hosting—using a declarative configuration. It abstracts away infrastructure provisioning so you can focus on code, but that abstraction comes with trade-offs you need to understand before production.

Think of AWS Amplify as a prefab house kit.
Plain-English First

Think of AWS Amplify as a prefab house kit. You get walls, roof, and plumbing pre-designed—you just assemble them. It's fast and works for most homes, but if you want a custom foundation or weird roof angle, you're fighting the kit. Traditional AWS is like building from scratch: slower, but you control every nail.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

You've seen the demo: 'Deploy a full-stack app in 5 minutes!' Then you try it in production, and at 3 AM your auth breaks because of a Lambda cold start you can't tune. AWS Amplify promises speed, but speed without understanding is just debt. Here's what everyone gets wrong: Amplify isn't a replacement for knowing AWS—it's a shortcut that works until it doesn't. This article will teach you when to use it, when to run, and how to avoid the fires I've seen burn teams.

Why Amplify Exists: The Pain of Manual AWS Setup

Before Amplify, building a full-stack app on AWS meant juggling CloudFormation stacks, IAM roles, and API Gateway configs. A simple user auth flow required Cognito, Lambda, and DynamoDB—each with its own console and CLI. Amplify condenses that into a single amplify push command. But the trade-off is that you lose visibility into the generated CloudFormation templates. When something breaks, you're debugging a black box. I've seen teams spend days trying to figure out why their API endpoint returns 500, only to find Amplify generated a wrong IAM policy. The lesson: always review the generated templates in amplify/backend/ before deploying to production.

amplify-init.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// io.thecodeforge — DevOps tutorial

# Initialize Amplify project
amplify init

# Add auth and API
amplify add auth
amplify add api

# Deploy
amplify push --yes

# Check generated CloudFormation
ls amplify/backend/api/yourApiName/build/
Output
✔ Successfully created initial resources.
✔ Generated CloudFormation templates in amplify/backend/api/yourApiName/build/
⚠ Production Trap:
Never run amplify push --yes in production without reviewing the generated CloudFormation. I've seen it overwrite existing DynamoDB tables because the schema changed.

Amplify's Auth: More Than Just Sign In

Amplify's Auth module wraps Amazon Cognito. It gives you sign-up, sign-in, MFA, and social login with a few CLI prompts. But here's the catch: Amplify's default configuration uses a single user pool for all environments (dev, prod). That means dev users can accidentally sign in to prod. Always separate user pools per environment. Also, the default password policy is weak—you must customize it. The real power of Amplify Auth is the pre- and post-authentication Lambda triggers. Use them to enforce custom business rules, like blocking users from certain IP ranges. But remember: these Lambdas count against your account concurrency. I've seen a misconfigured trigger bring down auth for all users.

custom-auth-trigger.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// io.thecodeforge — DevOps tutorial

exports.handler = async (event) => {
  // Pre sign-up trigger: block users from non-corporate email domains
  const allowedDomains = ['company.com', 'partner.com'];
  const email = event.request.userAttributes.email;
  const domain = email.split('@')[1];
  
  if (!allowedDomains.includes(domain)) {
    throw new Error('Only corporate emails allowed');
  }
  
  return event;
};
Output
User is created only if email domain is allowed. Otherwise, sign-up fails with 'Only corporate emails allowed'.
Try it live
💡Senior Shortcut:
Use Amplify's pre-token-generation Lambda to add custom claims to the JWT. This avoids extra DB lookups on every API call.

GraphQL API: The Double-Edged Sword

Amplify's API module defaults to AWS AppSync with GraphQL. It auto-generates resolvers and DynamoDB tables from a schema. This is incredibly fast for CRUD apps. But the auto-generated resolvers are naive. They don't handle pagination well, and they can't do complex joins. You'll quickly hit the 1MB response limit or see high latency on nested queries. The fix: write custom VTL or JavaScript resolvers. Also, Amplify's default authorization uses IAM, which is fine for server-side, but for client-side, use Cognito User Pools or OIDC. Never expose IAM credentials in client code—I've seen that in production.

schema.graphqlGRAPHQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// io.thecodeforge — DevOps tutorial

type Todo @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  name: String!
  description: String
  createdAt: AWSDateTime!
}

# Custom query with pagination
type Query {
  listTodosByDate(limit: Int, nextToken: String): PaginatedTodos!
}

type PaginatedTodos {
  items: [Todo]
  nextToken: String
}
Output
Generates DynamoDB table, AppSync API, and resolvers. Owner authorization ensures users only see their own todos.
⚠ Never Do This:
Using @auth with allow: public exposes your API to the internet. I've seen a startup's entire database leaked because they forgot to change it from the default.

Storage: S3 Made Simple, But Watch Permissions

Amplify Storage wraps S3 with a simple API for uploads and downloads. It automatically sets up a bucket and Cognito-based access. The default policy gives authenticated users access to their own folder (protected/). But here's the trap: the default bucket policy allows public read access to the protected/ prefix if you enable 'guest access'. That means anyone with the bucket URL can read any file. Always review the generated bucket policy. Also, Amplify doesn't set up lifecycle rules—your old files will accumulate and cost money. Add a lifecycle policy to expire old objects.

storage-upload.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// io.thecodeforge — DevOps tutorial

import { Storage } from 'aws-amplify';

async function uploadProfilePicture(file) {
  try {
    // Upload to user's protected folder
    const result = await Storage.put(
      `profile/${file.name}`,
      file,
      {
        contentType: file.type,
        level: 'protected', // only owner and admins can read
      }
    );
    console.log('Upload succeeded:', result.key);
  } catch (error) {
    console.error('Upload failed:', error);
  }
}
Output
File uploaded to S3 under protected/{identityId}/profile/filename.jpg
Try it live
🔥Production Trap:
Amplify's default S3 bucket has versioning disabled. If a user uploads a file with the same key, it overwrites silently. Enable versioning for audit trails.

Hosting: CI/CD That Works—Until It Doesn't

Amplify Hosting provides continuous deployment from Git branches. It builds and deploys your frontend and backend together. This is great for small teams. But the build environment is limited: 1GB memory, 30-minute build timeout, and no custom Docker images. If your build needs more resources, you're stuck. Also, Amplify Hosting doesn't support custom build scripts for the backend—it only runs amplify push. For complex backends, you're better off using AWS CodePipeline. Another gotcha: Amplify Hosting's default domain (yourApp.amplifyapp.com) has a TLS certificate that auto-renews, but custom domains require manual DNS verification. I've seen teams forget to update nameservers and lose HTTPS for days.

amplify.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// io.thecodeforge — DevOps tutorial

version: 1
backend:
  phases:
    build:
      commands:
        - amplifyPush --simple
frontend:
  phases:
    preBuild:
      commands:
        - npm ci
    build:
      commands:
        - npm run build
  artifacts:
    baseDirectory: build
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*
Output
Builds and deploys frontend and backend on every push to the connected branch.
⚠ The Classic Bug:
If your build fails with 'Error: Cannot find module', check that npm ci runs in preBuild, not build. Amplify's build phase has a different working directory.

When to Ditch Amplify: The Breaking Point

Amplify is great for MVPs and internal tools. But I've seen it break at scale. The breaking points: 1) Complex authorization rules beyond owner/group—Amplify's @auth directive can't handle attribute-based access control. 2) Multi-region deployments—Amplify doesn't support deploying to multiple regions from one project. 3) Custom infrastructure—if you need a Redis cluster or a custom VPC, Amplify's abstraction gets in the way. 4) High-traffic APIs—Amplify's auto-generated resolvers don't support DAX or DynamoDB auto-scaling well. When you hit these, migrate to raw CloudFormation or CDK. The migration is painful, but it's better than fighting Amplify.

💡Senior Shortcut:
Use Amplify for the frontend and hosting, but manage the backend with CDK. This gives you the best of both worlds: fast frontend deploys and full backend control.
● Production incidentPOST-MORTEMseverity: high

The 3 AM Auth Meltdown

Symptom
Users couldn't sign in for 15 minutes. CloudWatch showed 'UserNotFoundException' even for valid credentials.
Assumption
Team assumed a Cognito outage or misconfigured user pool.
Root cause
A Lambda function triggered by a DynamoDB stream was exhausting the account's concurrent execution limit (1000). Cognito's pre-authentication Lambda was throttled, causing silent failures.
Fix
Set reserved concurrency on the stream-processing Lambda to 100. Added error handling in the pre-auth trigger to fall back to default behavior on throttling.
Key lesson
  • Amplify's default Lambda configuration doesn't set concurrency limits—always set reserved concurrency for critical path functions.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.3 entries
Symptom · 01
Amplify build fails with 'Resource limit exceeded'
Fix
1. Check AWS Service Quotas for Lambda, DynamoDB, and AppSync. 2. Request limit increase if needed. 3. Reduce resource usage by consolidating functions.
Symptom · 02
GraphQL API returns 'Unauthorized' for authenticated users
Fix
1. Verify the user's Cognito group matches the @auth rule. 2. Check the generated resolver's IAM role. 3. Test with a static API key to isolate auth issue.
Symptom · 03
S3 uploads fail with 'Access Denied'
Fix
1. Check the bucket policy in S3 console. 2. Verify the Cognito identity pool's IAM role includes s3:PutObject. 3. Ensure the file key matches the protected/{identityId}/ prefix.
★ AWS Amplify Triage Cheat SheetFirst-response commands for when things go wrong—copy-paste ready.
Build fails with `Error: Cannot find module 'aws-amplify'`
Immediate action
Check if aws-amplify is in package.json dependencies, not devDependencies.
Commands
npm ls aws-amplify
cat package.json | grep aws-amplify
Fix now
Run npm install aws-amplify --save and commit package-lock.json.
API returns 500 with `Unable to resolve resource`+
Immediate action
Check the AppSync resolver logs in CloudWatch.
Commands
aws logs describe-log-groups --log-group-name-prefix /aws/appsync/
aws logs tail <log-group-name> --since 5m
Fix now
Update the resolver's request mapping template to reference the correct DynamoDB table ARN.
Auth sign-in fails with `UserNotFoundException` for valid users+
Immediate action
Check if a pre-authentication Lambda trigger is throttling.
Commands
aws lambda get-function-configuration --function-name <pre-auth-trigger>
aws cloudwatch get-metric-statistics --metric-name Throttles --namespace AWS/Lambda --statistics Sum --start-time <5min ago>
Fix now
Set reserved concurrency on the trigger Lambda to 1 or add error handling to fall back to default behavior.
S3 upload fails with `Access Denied` for authenticated users+
Immediate action
Check the bucket policy and the Cognito role.
Commands
aws s3api get-bucket-policy --bucket <bucket-name>
aws iam get-role-policy --role-name <Cognito-auth-role> --policy-name <policy-name>
Fix now
Add s3:PutObject permission for the protected/${cognito-identity.amazonaws.com:sub}/* prefix.
Feature / AspectAWS AmplifyRaw CloudFormation/CDK
Setup speedMinutesHours to days
CustomizationLimited to Amplify's abstractionsFull control over every resource
DebuggingBlack box—hard to trace errorsFull visibility into templates and logs
Multi-regionNot supportedNative support
Complex authBasic owner/group onlyAny IAM policy or Lambda authorizer
Build environment1GB memory, 30 min timeoutCustomizable via CodeBuild
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
amplify-init.shamplify initWhy Amplify Exists
custom-auth-trigger.jsexports.handler = async (event) => {Amplify's Auth
schema.graphqltype Todo @model @auth(rules: [{ allow: owner }]) {GraphQL API
storage-upload.jsasync function uploadProfilePicture(file) {Storage
amplify.ymlversion: 1Hosting: CI/CD That Works

Key takeaways

1
Amplify is a productivity tool, not a production platform—always review generated CloudFormation.
2
Set reserved concurrency on all Lambda triggers to prevent auth and API throttling.
3
Separate user pools per environment to avoid cross-environment sign-ins.
4
When your app needs custom infrastructure or multi-region, ditch Amplify for CDK.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Amplify handle Lambda concurrency for auth triggers, and what h...
Q02SENIOR
When would you choose Amplify over CDK for a production full-stack app?
Q03SENIOR
What happens when you add a new field to an Amplify GraphQL schema and p...
Q04JUNIOR
What is the difference between Amplify's @auth with 'owner' and 'group'?
Q05SENIOR
A user reports that their uploads fail intermittently with 403. How do y...
Q06SENIOR
How would you design a multi-tenant SaaS app using Amplify?
Q01 of 06SENIOR

How does Amplify handle Lambda concurrency for auth triggers, and what happens when they throttle?

ANSWER
Amplify doesn't set reserved concurrency on auth triggers by default. When they throttle, Cognito silently fails with a generic error. Mitigation: set reserved concurrency to 1-5 and add error handling in the trigger to return the event unmodified on throttling.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
Can I use AWS Amplify for a production app?
02
What's the difference between Amplify and AWS CDK?
03
How do I migrate from Amplify to CDK?
04
What happens when Amplify's default Lambda concurrency is exhausted?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 18, 2026
last updated
2,466
articles · all by Naren
🔥

That's AWS. Mark it forged?

3 min read · try the examples if you haven't

Previous
AWS SAM: Serverless Application Model
46 / 63 · AWS
Next
Amazon MSK: Managed Kafka Streaming