AppEngine is a backend. This section is about what you put in front of it.
Everything here — a Next.js storefront, a Flutter app, a page of plain HTML — consumes the same public API through the same rules. Pick a starting point below, or read the call path first if you want the model before the code.
Start here
Building for mobile
Reference
The call path
Browser ──▶ Your server ──▶ AppEngine
(1) (2)(1) The browser talks only to your own origin. Same-origin requests, your cookies, your session. (2) Your server talks to AppEngine. It holds the credentials and decides what the visitor was entitled to ask for.
Do not call AppEngine endpoints from a browser unless the request cannot be served any other way.
Three reasons, in order of severity:
Credentials. Server-to-server calls authenticate with an application token or an apiKey. Anything in browser JavaScript is readable by anyone who opens devtools — a token that reaches the browser is a token you have published.
Authority. AppEngine authorises the caller. If the caller is the browser, the visitor chooses the request — including the parts you assumed nobody would change, like the quantity, the price, or whose record is fetched.
Change. An endpoint called from a browser is called by every stale tab and cached bundle still in the wild. Behind your own server it is one deployment away from being changed.
When a direct call is fine
- Realtime. A WebSocket cannot be usefully proxied per message — see Realtime & chat.
- Mobile. A device has no server in front of it. App credentials in a binary are an identifier, not a secret; the signed-in person's token is what protects the request.
- Large uploads, where relaying bytes through your server buys nothing.
- Public, read-only, non-personalised data, identical for every visitor.
Money comes from the server
Prices, discounts, shipping and tax are computed by AppEngine and rendered by the client verbatim. Clients must not re-derive them.
The same cart is priced for a website, a mobile app and a point-of-sale terminal. Arithmetic repeated in a client is a second opinion, and the moment a price rule resolves differently server-side, that client shows a number nobody will honour.
// Wrong — three channels, three slightly different answers
const total = subtotal + shipping - discount;
// Right — one answer, from the one place that decides it
const { total } = await appmint.cart.price({ productItems, shippingAddress, couponCode });total already includes tax and shipping. Computing subtotal + shipping - discount drops the tax — and matches on a tax-free destination, which is exactly why the bug survives review. On a real order: 900 + 75 + 74.25 = 1049.25, not 975.