Local SDK vs HTTP/GraphQL API
Choose between the high-performance internal Local SDK and the external REST/GraphQL API in SveltyCMS.
On this page
Both access patterns are derived from a single, unified OpenAPI 3.1.0 contract, ensuring 1:1 parity and maximum flexibility for any setup:
- External HTTP / GraphQL API — The secure, public-facing interface with the full middleware stack (auth, rate limiting, CORS, etc.). Now featuring automated population and real-time subscriptions.
- Internal Local SDK — A zero-overhead, server-side-only API for maximum performance in your SvelteKit backend code.
Recommendation: In all server-side code (+page.server.ts, actions, hooks, etc.), prefer the Local SDK unless you have a specific reason to test the full HTTP stack.
When to Use Which
| Context | Recommended | Latency | Reason |
|---|---|---|---|
Client-side (.svelte, +page.ts) |
HTTP / GraphQL API | 50–150 ms | Browser environment; requires auth, CORS, and network. |
Server-side (.server.ts, actions) |
Local SDK | 2–15 µs | Direct function calls; zero serialization or network overhead. |
| Mobile / External App | GraphQL | 80–300+ ms | Optimized data fetching with nested population. |
| Plugins / Background Jobs | Local SDK | 2–15 µs | Full access to core logic and security context. |
Rule of thumb: If your code runs server-side (files ending in .server.ts or inside +server.ts routes), use the Local SDK.
Use GraphQL for external integrations that require complex nested data in a single request.
Why the Local SDK Exists
The Local SDK (src/services/sdk/index.ts — the LocalCMS class) is a high-performance facade that:
- Eliminates overhead: No network stack, JSON parsing, or serialization.
- Bypasses external security: Safely skips WAF and rate-limiting because it runs with server privileges.
- Calls Core Logic: Executes the exact same
modifyRequestpipeline as the HTTP layer. - Maintains Consistency: Automatically triggers cache invalidation and real-time events (SSE).
- Unified Context: Respects the same tenant isolation and permissions as the external API.
Measured Local SDK vs HTTP (SQLite, self-measured 2026-08)
Same machine, same harness (bun test tests/benchmarks/). The Local SDK is two to three orders of magnitude faster than the HTTP cold path because the HTTP middleware stack (auth, RBAC, security headers, serialization) is the dominant cost — not the database.
| Operation | Local SDK (in-process) | HTTP cold | HTTP turbo (response cache) |
|---|---|---|---|
find by _id |
64–127k RPS · 8-16 µs | 174 RPS · 5.6 ms | 408 RPS · 2.3 ms |
findById (warm L1) |
259–431k RPS · 2-3 µs | — | — |
create (full pipeline) |
2,000 RPS · 500 µs | — | — |
update (full pipeline) |
2,500 RPS · 400 µs | — | — |
create (detached) |
4,900 RPS · 200 µs | — | — |
update (detached) |
4,400 RPS · 230 µs | — | — |
Two write columns are reported deliberately and no environment flags are involved — the benchmark measures exactly what production runs. (full pipeline) = the SDK call with all post-write side effects executing (outbox event INSERT, cache-pattern invalidation, workflow init, pubsub). (detached) = the same call with the documented skipSideEffects: true option (used by bulk seed / high-throughput import paths). The ambient BENCHMARK env toggles were removed from the write path — benchmarks no longer measure a code path production never runs.
Adapter-level INSERT on the same machine is ~14k RPS (raw INSERT…RETURNING). The findMany-by-_id ultra path (routes list calls with a pure _id filter through the raw prepared findById) removed the previous ~10× list-path penalty.
Example: Local SDK Structure
The cms (LocalCMS) object provides a unified, typed interface to all system capabilities:
// Content Management
await cms.collections.find("posts", { limit: 10 });
await cms.collections.create("posts", { title: "New" });
await cms.media.list({ folderId: "uploads" });
// Identity & Access
await cms.auth.listUsers({ tenantId });
await cms.websiteTokens.create({
name: "API Key",
permissions: ["content:read"],
user,
tenantId,
});
// System & Infrastructure
await cms.system.settings.getAll();
await cms.system.getHealth();
await cms.widgets.list();
// Collection Builder organizational tree (gui-save, manifest, SSE)
await cms.contentStructure.saveGuiStructure(
[{ type: "move", node: { path: "/collection/posts", parentId: "cat-1" } }],
{ tenantId },
);
// Direct adapter access (escape hatch)
const raw = await cms.db.crud.findMany("posts", {}, { tenantId });
Example: Data Loading in +page.server.ts
import { LocalCMS } from "@src/services/sdk";
import { getDb } from "@src/databases/db";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ locals }) => {
const cms = new LocalCMS(getDb()!);
const posts = await cms.collections.find("posts", {
limit: 10,
tenantId: locals.tenantId,
});
return { posts };
};
Example: Bulk Operations
// Collections namespace supports bulkCreate, bulkUpdate, bulkDelete
const result = await cms.collections.bulkCreate("products", largeDataArray, {
tenantId,
});
Technical Details
Local SDK Guarantees
- Widget/request modification pipeline (
modifyRequest) runs identically to HTTP requests. - Cache hooks, invalidation, and version bumping happen automatically.
- Real-time updates (SSE / WebSocket) are triggered for connected clients.
- Multi-tenancy, user context, and permissions are applied consistently.
- Media Intelligent Storage: Shared deduplication and loop protection logic for all uploads via
cms.media.upload(). - Bulk operations:
cms.collections.bulkCreate(),bulkUpdate(),bulkDelete()for high-throughput tasks. - Direct adapter access:
cms.dbprovides escape-hatch access to the rawIDBAdapterwhen the SDK namespace doesn’t cover a needed operation.
When You Might Still Use HTTP from Server Code
- Calling a completely external microservice or third-party API.
- Deliberately exercising the full external middleware (e.g., for testing or logging).
- Cross-origin or cross-instance communication.
Best Practices
- Default to Local SDK in every
.server.tsfile, server actions, and hooks. - Avoid
fetch('/api/...')inside server code — it adds unnecessary latency and stack depth. - Instantiate via
new LocalCMS(adapter)from@src/services/sdkfor clean, typed access. The instance is lightweight and safe to create per-request. - Use
cms.dbwhen you need raw adapter access — e.g.,cms.db.system.websiteTokens.getByToken().
---
## Related
- [API Reference](/docs/development/index)
- [Getting Started](/docs/getting-started)