Skip to main content
Technology & EngineeringBaas330 lines

AWS Amplify

AWS Amplify BaaS with AppSync GraphQL, Cognito auth, S3 storage, and Lambda functions

Quick Summary25 lines
You are an expert in AWS Amplify for rapid backend development, including AppSync (GraphQL), Cognito authentication, S3 storage, Lambda functions, and Amplify Hosting.

## Key Points

- Use `npx ampx sandbox` during development — it creates an isolated cloud sandbox per developer, avoiding conflicts in shared environments.
- Define authorization rules on every model using the `.authorization()` chain; combine `allow.owner()` with `allow.publicApiKey().to(['read'])` for common blog-like patterns.
- Use Amplify Gen 2's typed client (`generateClient<Schema>()`) for full type safety from schema to frontend.
- Leverage `a.belongsTo()` and `a.hasMany()` relationships to get efficient GraphQL resolvers with DynamoDB single-table design.
- Store secrets using Amplify's secret management (`npx ampx sandbox secret set`) rather than hardcoding in resource files.
- **Missing authorization rules**: Models without `.authorization()` are inaccessible by default — unlike some other BaaS platforms, Amplify defaults to deny.
- **Gen 1 vs. Gen 2 confusion**: Gen 2 uses TypeScript definitions (no `amplify push` CLI prompts); do not mix Gen 1 CLI commands with a Gen 2 project.
- **Cold starts**: Lambda functions behind AppSync experience cold starts; use provisioned concurrency for latency-sensitive resolvers.
- **Large file uploads**: Use the `uploadData` API which handles multipart uploads automatically; do not try to send large files through AppSync mutations.

## Quick Example

```typescript
// src/main.ts
import { Amplify } from 'aws-amplify';
import outputs from '../amplify_outputs.json';

Amplify.configure(outputs);
```
skilldb get baas-skills/AWS AmplifyFull skill: 330 lines
Paste into your CLAUDE.md or agent config

AWS Amplify — Backend as a Service

You are an expert in AWS Amplify for rapid backend development, including AppSync (GraphQL), Cognito authentication, S3 storage, Lambda functions, and Amplify Hosting.

Core Philosophy

Overview

AWS Amplify is a set of tools and services for building full-stack applications on AWS. Amplify Gen 2 uses a TypeScript-first, code-based approach to define backends. It provisions AWS AppSync (managed GraphQL), Amazon Cognito (authentication), Amazon S3 (storage), AWS Lambda (functions), and Amazon DynamoDB (database) — all configured through TypeScript code rather than CLI wizards. Amplify Hosting provides CI/CD and global CDN deployment for frontends.

Setup & Configuration

Project Setup (Gen 2)

# Create a new Amplify project
npm create amplify@latest

# Or add to existing project
npm install aws-amplify @aws-amplify/backend
npx ampx sandbox  # start local cloud sandbox

Client Configuration

// src/main.ts
import { Amplify } from 'aws-amplify';
import outputs from '../amplify_outputs.json';

Amplify.configure(outputs);

Backend Definition Structure

amplify/
  auth/
    resource.ts       # Cognito configuration
  data/
    resource.ts       # AppSync + DynamoDB schema
  storage/
    resource.ts       # S3 configuration
  functions/
    my-function/
      resource.ts     # Lambda configuration
      handler.ts      # Lambda code
  backend.ts          # Combines all resources

Backend Entry Point

// amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { data } from './data/resource';
import { storage } from './storage/resource';

defineBackend({
  auth,
  data,
  storage,
});

Core Patterns

Authentication (Cognito)

// amplify/auth/resource.ts
import { defineAuth } from '@aws-amplify/backend';

export const auth = defineAuth({
  loginWith: {
    email: true,
    externalProviders: {
      google: {
        clientId: 'google-client-id',
        clientSecret: 'google-client-secret',
      },
      callbackUrls: ['http://localhost:3000/'],
      logoutUrls: ['http://localhost:3000/'],
    },
  },
  userAttributes: {
    preferredUsername: { required: true },
  },
});
// Client-side auth
import { signUp, signIn, signOut, getCurrentUser, fetchAuthSession } from 'aws-amplify/auth';

// Sign up
await signUp({
  username: 'user@example.com',
  password: 'secure-password',
  options: {
    userAttributes: { preferred_username: 'alice' },
  },
});

// Sign in
const { isSignedIn } = await signIn({
  username: 'user@example.com',
  password: 'secure-password',
});

// Get current user
const { username, userId } = await getCurrentUser();

// Get session/tokens
const { tokens } = await fetchAuthSession();
console.log(tokens?.idToken?.toString());

// Sign out
await signOut();
// React: Authenticator component
import { Authenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

function App() {
  return (
    <Authenticator>
      {({ signOut, user }) => (
        <div>
          <p>Welcome {user.username}</p>
          <button onClick={signOut}>Sign out</button>
        </div>
      )}
    </Authenticator>
  );
}

Data (AppSync + DynamoDB)

// amplify/data/resource.ts
import { defineData, a, type ClientSchema } from '@aws-amplify/backend';

const schema = a.schema({
  Post: a.model({
    title: a.string().required(),
    body: a.string().required(),
    published: a.boolean().default(false),
    author: a.belongsTo('User', 'authorId'),
    authorId: a.id(),
    comments: a.hasMany('Comment', 'postId'),
  }).authorization((allow) => [
    allow.publicApiKey().to(['read']),
    allow.owner(),
  ]),

  Comment: a.model({
    body: a.string().required(),
    post: a.belongsTo('Post', 'postId'),
    postId: a.id(),
  }).authorization((allow) => [
    allow.publicApiKey().to(['read']),
    allow.authenticated().to(['create']),
    allow.owner().to(['update', 'delete']),
  ]),

  User: a.model({
    name: a.string().required(),
    email: a.string().required(),
    posts: a.hasMany('Post', 'authorId'),
  }).authorization((allow) => [
    allow.owner(),
    allow.authenticated().to(['read']),
  ]),
});

export type Schema = ClientSchema<typeof schema>;
export const data = defineData({ schema });
// Client-side data operations
import { generateClient } from 'aws-amplify/data';
import type { Schema } from '../amplify/data/resource';

const client = generateClient<Schema>();

// Create
const { data: post } = await client.models.Post.create({
  title: 'My Post',
  body: 'Content here',
  published: true,
});

// Read
const { data: post } = await client.models.Post.get({ id: postId });

// List with filters
const { data: posts } = await client.models.Post.list({
  filter: { published: { eq: true } },
});

// Update
const { data: updated } = await client.models.Post.update({
  id: postId,
  title: 'Updated Title',
});

// Delete
await client.models.Post.delete({ id: postId });

// Real-time subscription
const sub = client.models.Post.observeQuery({
  filter: { published: { eq: true } },
}).subscribe({
  next: ({ items }) => {
    console.log('Posts:', items);
  },
});
// Cleanup: sub.unsubscribe();

Storage (S3)

// amplify/storage/resource.ts
import { defineStorage } from '@aws-amplify/backend';

export const storage = defineStorage({
  name: 'myAppStorage',
  access: (allow) => ({
    'profile-pictures/{entity_id}/*': [
      allow.entity('identity').to(['read', 'write', 'delete']),
    ],
    'public/*': [
      allow.guest.to(['read']),
      allow.authenticated.to(['read', 'write', 'delete']),
    ],
  }),
});
// Client-side storage
import { uploadData, getUrl, remove } from 'aws-amplify/storage';

// Upload
const result = await uploadData({
  path: `profile-pictures/${userId}/avatar.jpg`,
  data: file,
  options: { contentType: 'image/jpeg' },
}).result;

// Get URL
const { url } = await getUrl({
  path: `profile-pictures/${userId}/avatar.jpg`,
});

// Delete
await remove({ path: `profile-pictures/${userId}/avatar.jpg` });

Functions (Lambda)

// amplify/functions/process-order/resource.ts
import { defineFunction } from '@aws-amplify/backend';

export const processOrder = defineFunction({
  name: 'process-order',
  entry: './handler.ts',
  timeoutSeconds: 30,
  memoryMB: 256,
  environment: {
    STRIPE_KEY: 'stripe-secret-key',
  },
});
// amplify/functions/process-order/handler.ts
import type { Handler } from 'aws-lambda';

export const handler: Handler = async (event) => {
  const { orderId, items } = JSON.parse(event.body || '{}');

  // Process the order
  return {
    statusCode: 200,
    body: JSON.stringify({ success: true, orderId }),
  };
};

Best Practices

  • Use npx ampx sandbox during development — it creates an isolated cloud sandbox per developer, avoiding conflicts in shared environments.
  • Define authorization rules on every model using the .authorization() chain; combine allow.owner() with allow.publicApiKey().to(['read']) for common blog-like patterns.
  • Use Amplify Gen 2's typed client (generateClient<Schema>()) for full type safety from schema to frontend.
  • Leverage a.belongsTo() and a.hasMany() relationships to get efficient GraphQL resolvers with DynamoDB single-table design.
  • Store secrets using Amplify's secret management (npx ampx sandbox secret set) rather than hardcoding in resource files.

Common Pitfalls

  • Missing authorization rules: Models without .authorization() are inaccessible by default — unlike some other BaaS platforms, Amplify defaults to deny.
  • Gen 1 vs. Gen 2 confusion: Gen 2 uses TypeScript definitions (no amplify push CLI prompts); do not mix Gen 1 CLI commands with a Gen 2 project.
  • Cold starts: Lambda functions behind AppSync experience cold starts; use provisioned concurrency for latency-sensitive resolvers.
  • DynamoDB query limitations: The generated DynamoDB tables may not support all filter combinations efficiently; use secondary indexes defined with a.secondaryIndex() for common access patterns.
  • Large file uploads: Use the uploadData API which handles multipart uploads automatically; do not try to send large files through AppSync mutations.

Anti-Patterns

Over-engineering for hypothetical requirements. Building for scenarios that may never materialize adds complexity without value. Solve the problem in front of you first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide wastes time and introduces risk.

Premature abstraction. Creating elaborate frameworks before having enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at system boundaries. Internal code can trust its inputs, but boundaries with external systems require defensive validation.

Skipping documentation. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add baas-skills

Get CLI access →