Performance & Optimization
At scale, the difference between a well-optimised API integration and a naive one can mean the difference between sub-second dashboards and timeouts, or staying within rate limits versus being blocked. This guide covers the patterns that matter most when building against the Lumar GraphQL API.
Pagination page size
All connection fields accept a first or last argument between 1 and 1,000. Choosing the right page size depends on your use case:
| Use case | Recommended page size |
|---|---|
| UI rendering | 20–100 |
| Data export / processing | 100–500 |
Always use pageInfo.hasNextPage combined with endCursor to drive iteration. Do not guess at offsets or rely on totalCount alone to calculate page boundaries.
Here is a complete example of iterating through all projects in an account:
query GetAllProjects($accountId: ObjectID!, $after: String) {
getAccount(id: $accountId) {
projects(first: 100, after: $after) {
nodes {
id
name
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
}
Loop through all pages in TypeScript:
async function fetchAllProjects(accountId: string): Promise<Project[]> {
const allProjects: Project[] = [];
let after: string | null = null;
let hasNextPage = true;
while (hasNextPage) {
const result = await executeQuery(GET_ALL_PROJECTS, { accountId, after });
const { nodes, pageInfo } = result.data.getAccount.projects;
allProjects.push(...nodes);
hasNextPage = pageInfo.hasNextPage;
after = pageInfo.endCursor;
}
return allProjects;
}
Fetch totalCount on the first page only — omit it from subsequent requests to avoid the extra computation on every page.
Prefer nodes over edges when you don't need cursors
GraphQL connections expose two equivalent ways to access items. nodes is shorthand for edges.node and produces cleaner, less verbose queries. Use it by default.
# Simpler — use when you don't need per-item cursors
nodes {
id
name
}
# Use when you need individual item cursors
edges {
cursor
node {
id
name
}
}
Only reach for edges when you need the per-item cursor value — for example, when resuming a partially-completed export from a specific position in a large result set.
Batching multiple operations
The API supports request batching — multiple GraphQL operations sent in a single HTTP request as a JSON array. This reduces round trips and TCP connection overhead when you have several independent operations to fire at once.
When using Apollo Client, configure BatchHttpLink:
import { BatchHttpLink } from "@apollo/client/link/batch-http";
const batchLink = new BatchHttpLink({
uri: "https://api.lumar.io/graphql",
batchMax: 25, // up to 25 operations per request
batchInterval: 20, // wait 20ms to collect operations before sending
});
The batched request body is a JSON array. The Lumar API supports this format natively — no extra configuration is required on the server side.
Good use cases for batching:
- Creating or deleting many alerts in a single user action.
- Fetching multiple independent resources (e.g. several crawl statuses) in parallel without composing a multi-root query.
Polling for live crawl status
When monitoring an active crawl, poll at intervals appropriate to the current state rather than opening a persistent connection:
| State | Poll interval |
|---|---|
| Active crawl | every 5 seconds |
| Idle / background monitoring | every 60 seconds |
Always stop polling on error to avoid hammering a failing endpoint.
// Apollo Client example
const { startPolling, stopPolling } = useQuery(GET_CRAWL_STATUS, {
variables: { crawlId },
onError: () => stopPolling(),
});
useEffect(() => {
if (crawlIsActive) {
startPolling(5000); // 5s while crawl is running
} else {
stopPolling();
}
return () => stopPolling();
}, [crawlIsActive]);
Do not poll at short intervals (under 5 seconds) for non-critical status checks. Sustained high-frequency polling across multiple crawls can push you toward the rate limit of 6,000 requests per 5-minute window.
Reduce round trips with multi-root queries
GraphQL allows multiple root fields in a single query. Instead of making three separate HTTP requests for account information, a project list, and reference data, combine them:
query DashboardBootstrap($accountId: ObjectID!) {
getAccount(id: $accountId) {
name
subscription {
plan {
name
}
}
projects(first: 20) {
nodes {
id
name
primaryDomain
}
totalCount
}
}
getReportTemplates {
nodes {
id
name
}
}
}
This pattern is especially valuable on application startup or page load, where latency is most noticeable.
See the Advanced Query Patterns page for more examples of composing efficient multi-root queries.