Tutorials / Search / Backend integrations / Build a full app on LingCode Cloud
๐Ÿ“ Written โ— Intermediate Updated 2026-06-21

Build a full app on LingCode Cloud, end to end

TL;DR: A LingCode Cloud backend is a private Postgres database (plus auth, storage, realtime, functions, vector search) reached over HTTPS. You work in two layers: the schema (migrations, RLS, VIEWs, RPC functions โ€” full SQL, written once by the agent) and the browser SDK (per-table CRUD โ€” no JOINs, no upserts, 200-row pages). A mobile app, a web frontend, and a web API should all point at one backend and one schema; you separate users with row-level security, not with extra backends. Host the web build with the IDE's Cloud button (static โ†’ lingcode.dev/apps/<id>, or full-stack โ†’ <slug>.run.lingcode.dev). Get the conventions right up front and the agent builds the whole thing from a prompt.

Most LingCode Cloud docs cover one product at a time โ€” database, auth, hosting. This guide is the opposite: it walks the whole arc of shipping a real app โ€” backend, mobile API, web frontend, and web API โ€” and answers the question those pages don't: how do you keep several clients on one backend without their tables, routes, and data colliding? It's layered โ€” the first half is how to prompt the agent to do it; the second half is the hand-coding reference for when you want lower-level control.

What you'll learn

1. What LingCode Cloud is

When you're signed in, every project you open in the Mac IDE (or in /try) can reach a managed backend: a private Postgres database that LingCode operates for you, fronted by an HTTPS gateway. There's no connection string to manage and no server to run. The backend ships with:

Under the hood each backend is an isolated Postgres schema with its own database role. That isolation is a hard wall: one backend literally cannot read another's data. Remember that โ€” it's the reason behind the "one backend, many clients" rule below.

2. The two layers you build in

This is the single most important concept on the page. Everything else follows from it. There are two ways to touch your data, and they have different powers on purpose.

LayerWho writes itWhat it can do
Schema layer
(migrations, RLS, VIEWs, RPC functions, custom functions)
The agent (or you), once, via migrations Full Postgres. JOINs, CTEs, aggregates, upserts, indexes, constraints, generated columns, triggers, server-side SQL functions.
Browser SDK
(lingcode.from('table')โ€ฆ, runs in your shipped app)
Your app code, on every request Per-table CRUD only. No JOINs, no upserts, max 200 rows per read. update and delete refuse to run without a filter.

The mental model

Put relational and heavy work in the schema โ€” define a VIEW or an rpc() function once, then read it from the browser like a plain table. Keep the browser SDK for simple reads and writes. If you find yourself wanting a JOIN or an upsert in app code, that's the signal to push it down a layer into a migration.

So the data API you call from a shipped app is deliberately small and safe. The full power of Postgres is still there โ€” it just lives one level down, in your schema, where the agent writes it once. The rest of this guide leans on that split constantly.

3. One backend, many clients

Here's the question this guide exists to answer: you want a mobile app, a web frontend, and maybe some web API routes. Do they each get a backend?

No. They share one backend and one schema. A "mobile API" and a "web API" on LingCode aren't separate servers you stand up โ€” they're the same gateway and the same tables, reached by different clients:

What keeps a user's data private isn't a separate backend per surface โ€” it's row-level security. Each signed-in user gets a session; the gateway pins reads and writes to that user's id; your RLS policies do the rest. The mobile app and the web app see exactly the same rows for the same user because they are the same rows.

Backends are hard-isolated โ€” don't split by accident. If you provision a second backend for your "mobile API," the web app's key can't read it and you cannot JOIN across the two. They're separate databases. You'd be stuck proxying data between them through a function. Only provision a second backend when it's a genuinely separate product with its own users โ€” not just a second client of the same app.

When a client legitimately needs another backend's data (say, an admin tool reading a separate product), don't reach across โ€” expose it through a function or an HTTP endpoint on the owning backend and call that. The wall stays intact and the contract is explicit.

4. Naming conventions (the no-conflict rules)

Because every client shares one schema, naming is what keeps them from stepping on each other. None of this is enforced by the platform โ€” it's the convention that makes a multi-client app stay legible.

DoDon't
One workouts table for web + mobile, separated by RLSOne table per client (web_workouts, mobile_workouts)
user_id + an owner RLS policy on every private table"We'll filter by user in the app code"
Slugs like web-checkout / mobile-pushTwo functions both called handler
A second backend only for a separate productA second backend for the "mobile version" of the same app

5. How to prompt the agent

You rarely write the schema by hand โ€” you describe the app and let the agent build it against the live backend. It has direct tools for provisioning, migrations, and CRUD, and it works in a fixed order. A good prompt does three things: names the tables and who owns them, states the RLS intent, and says which logic must live server-side.

Tell the agent to inspect first

The backend's exact limits (tier, function timeout, whether long-running compute is available) are read live, not assumed. A reliable habit: start prompts with "check the backend's capabilities before designing" so the agent calls describe_backend and designs to what's actually there โ€” rather than reaching for localStorage or inventing an external service.

A prompt for the exact scenario in this guide โ€” one backend, web + mobile, no collisions:

Build a fitness app on the LingCode Cloud backend. First check the backend
capabilities, then provision it if needed and apply migrations.

ONE backend, ONE schema, shared by a web app and a mobile client.

Tables (snake_case, no prefixes):
  - workouts      (user-owned: user_id + owner RLS policy)
  - goals         (user-owned: user_id + owner RLS policy)
  - leaderboard   (public read, no user_id; writes only from a function)

Auth: email/password + Google sign-in. Use the built-in auth users table;
foreign-key user_id to it.

Server-only logic (put these in functions, not the browser):
  - billing-stripe-webhook   (verify + record payments)
  - leaderboard-recompute    (scheduled; writes the public leaderboard)

The web frontend uses the SDK directly; the mobile client hits the same
gateway. Do NOT create separate backends or per-client tables โ€” separate
users with RLS. Add indexes for the feed query, and expose the workout
feed as a VIEW so the client reads it without a JOIN.

Behind that prompt the agent follows a consistent sequence:

  1. Inspect the backend's capabilities and current tables.
  2. Provision the backend if the project doesn't have one yet (idempotent โ€” safe to repeat).
  3. Apply migrations โ€” CREATE TABLE, RLS policies, indexes, VIEWs, and any RPC functions.
  4. Build the app against it with normal CRUD calls, and create the server-side functions.

You can also drive each step explicitly โ€” "add a notifications table owned by the user, with realtime enabled" โ€” and the agent migrates the schema in place. Schema changes are migrations, so they're versioned and repeatable rather than one-off edits.

6. Making your project deployable

The backend is one half; hosting the app is the other. In the Mac IDE the Cloud button in the toolbar builds your project and ships it to a live HTTPS URL. There are two tiers, and which one you get is detected from your project:

What makes a project deployable, concretely:

Edge runtime, not Node. Full-stack apps run as edge workers โ€” a V8 isolate, not a Node server โ€” with a roughly 30-second cap per request. Long-running work (big imports, headless browsers, multi-minute jobs) belongs in a function, not an API route. See the best-practices note below.

7. Hand-coding reference

If you'd rather wire the SDK yourself โ€” in your own Next.js project, a plain HTML page, or a mobile client โ€” here's the lower-level surface. (Inside /try and the Mac preview this is all pre-wired as window.lingcode; you only do this when you take the app elsewhere. The dedicated walkthrough is Use LingCode Cloud in your own app.)

Create a client

You need two public values from lingcode.dev/backends โ†’ your backend โ†’ connection details: the data URL and the anon key. The anon key is meant to ship in the browser โ€” access is enforced by RLS on the server, not by hiding the key.

<script src="https://lingcode.dev/sdk/lingcode-v1.js"></script>
<script>
  const lingcode = LingCode.createClient(
    'https://lingcode.dev/api/cloud/be/<your-backend-id>',
    '<your-anon-key>'
  );
  await lingcode.ready;                 // settles any sign-in redirect first
  const user = lingcode.auth.getUser(); // { id, email } | null
</script>

Read and write (the CRUD layer)

// read โ€” filters chain before the verb; returns { data, error }
const { data, error } = await lingcode
  .from('workouts')
  .eq('user_id', user.id)
  .order('created_at', { ascending: false })
  .limit(50)
  .select();

// write
await lingcode.from('workouts').insert({ title: 'Morning run', user_id: user.id });
await lingcode.from('workouts').eq('id', 1).update({ title: 'Evening run' }); // filter required
await lingcode.from('workouts').eq('id', 1).delete();                          // filter required

Filter operators: .eq .neq .gt .gte .lt .lte .like .ilike .in(col,[โ€ฆ]) .is(col,null), and .match({a:1,b:2}). Multiple filters AND together.

Auth, storage, realtime, vector

The escape hatch for anything relational

The browser SDK is CRUD-only, so when you need a JOIN, an aggregate, or an upsert, push it into the schema: create a VIEW and select() from it like a table, or write an SQL function and call it. Have the agent add it in a migration; then your app code stays simple and reads one "table." Mobile and web both reuse the same view โ€” no duplicated query logic.

8. Best practices & quotas

What's next