One successful round-trip from a Next.js app to AppEngine. By the end you'll have a server component that fetches contacts from your org and renders them in a list. We're not teaching everything here — just one solid call you can build on.
Prerequisites
- Node 20+ and pnpm (or npm/yarn).
- An
orgidand API key from Sign up. - At least one contact in your org. If you don't have one, create it from the admin or with
curl -X POST .../data/contact -d '{"data":{"email":"[email protected]"}}'.
- 1
Create the app
pnpm create next-app@latest hello-appmint --ts --app --tailwind --eslint cd hello-appmint pnpm add axiosThe App Router is the canonical setup — that's what
base-appandyugouse. - 2
Add environment variables
Create
.env.localat the project root:APPENGINE_ENDPOINT=https://appengine.appmint.io ORG_ID=your-org-id APPMINT_API_KEY=amk_your_long_lived_keyNever commit this file. The
APPMINT_API_KEYis server-only — keep it out of anyNEXT_PUBLIC_*variable so it doesn't ship to the browser. - 3
Write the API client
Create
src/lib/appmint-client.ts. This is a stripped-down version of the canonical client used inbase-app— enough for a server-side fetch:// src/lib/appmint-client.ts import axios from 'axios'; const host = process.env.APPENGINE_ENDPOINT!; const orgId = process.env.ORG_ID!; const apiKey = process.env.APPMINT_API_KEY!; const client = axios.create({ baseURL: host, headers: { orgid: orgId, 'x-api-key': apiKey, 'Content-Type': 'application/json', }, }); export async function findData<T = any>( datatype: string, query: Record<string, any> = {}, options: Record<string, any> = { pageSize: 50 }, ) { const res = await client.post(`/repository/find/${datatype}`, { query, options }); return res.data as { data: Array<{ data: T; sk: string }> }; }Two things to notice:
- The
orgidheader is sent on every request. AppEngine rejects calls without it. - Records come back wrapped in
BaseModel\<T\>. The payload is inrecord.data, not on the record itself. See Concepts.
- The
- 4
Render contacts in a server component
Replace
src/app/page.tsx:// src/app/page.tsx import { findData } from '@/lib/appmint-client'; type Contact = { email?: string; firstName?: string; lastName?: string; }; export default async function Home() { const { data } = await findData<Contact>('contact', {}, { pageSize: 20 }); return ( <main className="p-8"> <h1 className="text-2xl font-semibold mb-4">Contacts</h1> <ul className="space-y-2"> {data.map((row) => ( <li key={row.sk} className="border rounded p-3"> <div className="font-medium"> {row.data.firstName ?? ''} {row.data.lastName ?? ''} </div> <div className="text-sm text-neutral-500">{row.data.email}</div> </li> ))} </ul> </main> ); }This is a server component. The fetch happens on the Next.js server, not the browser, so the API key never leaves your infrastructure.
- 5
Run it
pnpm devOpen
http://localhost:3000. You should see your contacts listed.If you see an empty list, the call succeeded but you have no contacts — create one. If you see a 400, the
orgidheader is missing or wrong. If you see a 401, the API key is invalid or scopes don't includeread.
What just happened
Your server component called POST /repository/find/contact with { query: {}, options: { pageSize: 20 } }. AppEngine resolved your API key, scoped the query to your org, and returned an array of BaseModel<Contact> wrappers. You rendered record.data for each row.
/repository/find/{datatype}JWT+APIKEYThat same endpoint pattern works for every collection in AppMint — products, orders, leads, pages. Swap contact for product and you're querying products. The shape of the payload changes; the call doesn't.
Patterns to grow into
When your client gets bigger than this file, lift the patterns from base-app/src/lib/:
appmint-config.ts— collect env in one typed module.appmint-endpoints.ts— endpoint URL constants, no hardcoded paths.repository-api.ts— generic CRUD against/repository/*.storefront-api.ts,events-api.ts, etc. — per-domain API modules.proxy-utils.ts— server-side proxy so browser calls go through/api/...without exposing the API key.
For sign-in flows and cookie-based JWT auth, the middleware pattern in base-app is the template — gate paths in middleware.ts and store the JWT in a token cookie.
Next
- Glossary
- Multi-tenancy — the
orgidheader in depth - Data model —
BaseModel\<T\>and optimistic concurrency