Subchapter 130.7
references/data-web.mdMarkdown6 KBView on GitHub
Prerequisites: Project initialized,
amplify_outputs.jsonexists (fromnpx ampx sandbox), andAmplify.configure(outputs)called in app entry point.Backend required: Data must be defined in
amplify/data/resource.tsusingdefineData— see data-backend.md.
Call
generateClient<Schema>()at module scope (outside any component). Calling it inside a component creates a new client on every render, breaking subscriptions, caching, and causing memory leaks.
import { generateClient } from 'aws-amplify/data';
import type { Schema } from '../amplify/data/resource';
// Module scope — called once
const client = generateClient<Schema>();The <Schema> generic gives full type inference on all model operations.
All operations return { data, errors }. You SHOULD check errors before using data.
const { data, errors } = await client.models.Todo.create({ content: 'Ship feature', priority: 'high' });Same shape for .list(), .get({ id }), .update({ id, done: true }), .delete({ id }).
.list() accepts an optional filter: { filter: { done: { eq: false } } }.
You SHOULD handle both GraphQL-level errors and network failures:
try {
const { data, errors } = await client.models.Todo.create({ content: 'New todo' });
if (errors) { /* handle GraphQL field/validation errors */ }
} catch (err) {
/* handle network or unexpected errors */
}observeQuery() — auto-updating list, returns { items } snapshots. Recommended default.onCreate() / onUpdate() / onDelete() — per-event subscriptions.Both return an observable; call .subscribe({ next }) and call sub.unsubscribe() in cleanup.
useEffect(() => {
const sub = client.models.Todo.observeQuery().subscribe({
next: ({ items }) => setTodos(items),
});
return () => sub.unsubscribe();
}, []);useEffect(() => {
const sub = client.models.Vote.observeQuery({
filter: { pollId: { eq: currentPollId } },
}).subscribe({
next: ({ items }) => setVotes(items),
});
return () => sub.unsubscribe();
}, [currentPollId]);| Pattern | Best For |
|---|---|
observeQuery() | Continuously updated lists — handles pagination, filtering, deduplication. Use by default. |
onCreate / onUpdate / onDelete | Fine-grained control — animations, toasts, or single event type only. |
observeQuerydoes NOT support server-side sorting. Sort results client-side after receiving them.
import { generateServerClientUsingCookies } from '@aws-amplify/adapter-nextjs/data';
import { cookies } from 'next/headers';
import outputs from '@/amplify_outputs.json';
import type { Schema } from '@/amplify/data/resource';
const cookieClient = generateServerClientUsingCookies<Schema>({ config: outputs, cookies });Use cookieClient.models.* the same as the browser client. Works in Server Components, Server Actions, and App Router API routes.
Identical to the web client — uses generateClient<Schema>() from aws-amplify/data.
All CRUD, observeQuery(), and subscription APIs (onCreate, onUpdate, onDelete) are the same.
// Backend: define with queryField name
.secondaryIndexes(index => [
index('pollId').sortKeys(['voterId']).queryField('votesByPollAndVoter')
])
// Frontend: call by name
const { data } = await client.models.Vote.votesByPollAndVoter({
pollId: currentPollId,
voterId: { eq: currentVoterId },
});a.json() fields require JSON.stringify() on write but auto-parse on read:
// Write: stringify before saving
await client.models.Config.create({
metadata: JSON.stringify({ key: "val", nested: { a: 1 } })
});
// Read: auto-parsed back to object
const { data } = await client.models.Config.get({ id });
console.log(data.metadata.key); // "val" — already an objectPassing a raw object on write fails silently with:
"Variable 'metadata' has an invalid value"(The
a.json()field maps to GraphQL’sAWSJSONscalar, which expects a JSON-encoded string as input.)
Array field updates = full replacement: Array fields have no append/remove operations. You must read, modify, and write the entire array:
const item = await client.models.Todo.get({ id });
const updated = [...(item.data?.tags ?? []), 'newTag'];
await client.models.Todo.update({ id, tags: updated });Risk: Concurrent updates can overwrite each other. For frequently-modified lists, consider a separate model with a relationship instead.
Subscription memory leaks: useEffect must return
() => sub.unsubscribe() as a cleanup function. Without it,
subscriptions accumulate across re-renders, causing memory leaks and
duplicate data updates.
Wrong auth mode for subscriptions: Subscriptions require a
WebSocket-compatible auth mode (userPool or iam). API key auth on
subscriptions fails silently.
Missing <Schema> generic: generateClient() without <Schema>
returns an untyped client — all operations lose autocomplete and type checking.
Server client without cookies: Using generateClient() in Next.js
server components fails (no browser session) — use
generateServerClientUsingCookies instead.