Docs / Search / LingCode Cloud / Best practices
๐Ÿ“˜ Reference โ— Production guide Updated 2026-06-11

Best practices & production guide

TL;DR: LingCode Cloud is fast to prototype on and forgiving in development โ€” but a few habits separate a demo from a backend you can ship. Model your data for one-table-at-a-time reads, paginate and count without scanning, index the columns you filter on, turn on row-level security everywhere the anon key can reach, keep private keys in the vault and server logic in functions, and stay inside your tier's quotas. Then run the pre-ship checklist.

Most of these rules fall out of one fact: the client SDK talks to a thin table-CRUD gateway, not to Postgres. That shapes how you model, how you read, and where security lives. Each section below is a short why plus concrete guidance, with the relevant guide linked so you can go deeper.

1. Model for table-CRUD

The runtime data API is one table at a time โ€” no JOINs, no upserts, and at most 200 rows per page. If you design a normalized schema and then try to stitch it back together with client-side fan-out, every screen costs a round-trip per relation. Model for your read paths instead.

-- a read-path view: join once in the DB, select() it like a table
create view order_lines_expanded as
select l.id, l.order_id, l.qty, p.name as product_name, p.price
from   order_lines l
join   products p on p.id = l.product_id;

See the Database guide for the full query-builder surface and the no-JOIN/upsert rule.

2. Paginate & count without scanning

select() returns at most 200 rows. Reach for pagination early โ€” it's not an optimization, it's the ceiling. Order by an indexed column and page with .limit() and .range().

const { data } = await lingcode
  .from('posts')
  .order('created_at', { ascending: false })  // indexed column
  .range(0, 49)                               // first 50
  .select();

Counting is the trap. Don't count by fetching every page and summing lengths โ€” that's an O(n) scan over the whole table on every view. Instead:

More on filters, ordering, and ranges in the Database guide.

3. Index what you filter and order by

An unindexed filter or sort scans the table. That's invisible at 50 rows and fatal at 50,000. In a migration, add an index for every column you filter or order by at scale, and for every foreign-key column.

create index if not exists idx_posts_author    on posts (author_id);
create index if not exists idx_posts_created_at on posts (created_at desc);

pgvector columns need a vector index once the table grows โ€” see the Vector search guide. And you don't have to find these by hand: the advisors scanner flags missing indexes and unindexed foreign keys for you. Run it before you ship โ€” see Scan your backend with advisors. Migrations basics are in the Database guide.

4. Security checklist

The anon key ships in your browser bundle, so the server is the only place security actually happens. Treat the database as hostile-by-default and let RLS gate every read and write.

The full policy model is in the Security guide.

5. Server logic & secrets

If a call needs a private key, it can't run in the browser โ€” the key would be visible to anyone who opens dev tools. Vendor API calls and any key-bearing logic belong in functions that read ctx.secrets:

// function โ€” the secret never reaches the client
export default async (req, ctx) => {
  const key = ctx.secrets.STRIPE_SECRET_KEY;
  // ...call the vendor server-side, return only what's safe...
};

Functions are short-lived, though. Two cases belong in a hosted Worker route instead of a function:

See the Functions guide and the Hosting guide.

6. Quotas & cost

Every tier has caps, and hitting one in production is a worse surprise than planning for it. Know yours before launch:

The backend flags storage at 80% and 95% โ€” watch for those warnings rather than waiting for a hard stop. And because functions are short-lived (โ‰ค30s), anything longer must be offloaded โ€” split the work, queue it, or move it to a hosted Worker. Tier caps are listed on the pricing page.

7. Migrations discipline

Schema drift between environments is the quiet killer of "works on my backend." Make migrations the only way schema changes โ€” never click-edit structure ad hoc.

create table if not exists invites (
  id         uuid primary key default gen_random_uuid(),
  email      text not null,
  created_at timestamptz not null default now()
);
create index if not exists idx_invites_email on invites (email);

Migration mechanics live in the Database guide.

8. Performance

Three habits keep an app responsive without extra infrastructure:

Pre-ship checklist

Before you flip an app to production, confirm:

  • RLS is on for every table the anon key can reach, and policies are written per command.
  • The advisors run clean โ€” no missing indexes, no unindexed foreign keys, no open tables.
  • There's an index for every hot filter and sort.
  • Secrets are in the vault โ€” no private keys anywhere in client code.
  • Defaults are set for every remote-config flag, so a missing value never breaks the app.
  • You're within tier quotas on tables, objects, storage, functions, function wall-clock, and emails/day.