Documentation

Hello World (Next.js)

Stand up a Next.js app, talk to AppEngine, render a list of contacts.

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 orgid and 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. 1

    Create the app

    pnpm create next-app@latest hello-appmint --ts --app --tailwind --eslint
    cd hello-appmint
    pnpm add axios
    

    The App Router is the canonical setup — that's what base-app and yugo use.

  2. 2

    Add environment variables

    Create .env.local at the project root:

    APPENGINE_ENDPOINT=https://appengine.appmint.io
    ORG_ID=your-org-id
    APPMINT_API_KEY=amk_your_long_lived_key
    

    Never commit this file. The APPMINT_API_KEY is server-only — keep it out of any NEXT_PUBLIC_* variable so it doesn't ship to the browser.

  3. 3

    Write the API client

    Create src/lib/appmint-client.ts. This is a stripped-down version of the canonical client used in base-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 orgid header is sent on every request. AppEngine rejects calls without it.
    • Records come back wrapped in BaseModel\<T\>. The payload is in record.data, not on the record itself. See Concepts.
  4. 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. 5

    Run it

    pnpm dev
    

    Open 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 orgid header is missing or wrong. If you see a 401, the API key is invalid or scopes don't include read.

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.

POST/repository/find/{datatype}JWT+APIKEY

That 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