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.
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
- ✓Basic knowledge of AWS services (Lambda, DynamoDB, S3)
- ✓Familiarity with React or a similar frontend framework
- ✓Comfort with CLI and Git workflows
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.
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.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
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'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.
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.
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.
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.
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.
The 3 AM Auth Meltdown
- Amplify's default Lambda configuration doesn't set concurrency limits—always set reserved concurrency for critical path functions.
npm ls aws-amplifycat package.json | grep aws-amplify| File | Command / Code | Purpose |
|---|---|---|
| amplify-init.sh | amplify init | Why Amplify Exists |
| custom-auth-trigger.js | exports.handler = async (event) => { | Amplify's Auth |
| schema.graphql | type Todo @model @auth(rules: [{ allow: owner }]) { | GraphQL API |
| storage-upload.js | async function uploadProfilePicture(file) { | Storage |
| amplify.yml | version: 1 | Hosting: CI/CD That Works |
Key takeaways
Interview Questions on This Topic
How does Amplify handle Lambda concurrency for auth triggers, and what happens when they throttle?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
That's AWS. Mark it forged?
3 min read · try the examples if you haven't