Chat

N+1 Queries and Database Indexing for Node APIs

How production Node and Next.js backends quietly die from N+1 queries — and the indexing, batching, and explain-plan habits that keep APIs fast without customers ever noticing.

End customers never open your MongoDB dashboard. They only feel latency. Most “the site feels slow” tickets we debug on Node and Next.js backends are not CSS — they are N+1 queries and missing indexes.

This post is a behind-the-scenes engineering playbook we use at ShubhKarma Tech when shipping admin panels, ecommerce APIs, and booking systems.

What an N+1 looks like in real APIs

You fetch a list of 50 orders, then for each order you query the customer, then the line items. That is 1 + 50 + 50 round trips. On a cold connection pool it feels fine in staging with 5 rows. In production with 5,000 rows it melts.

ORMs make this easy to miss: `await Order.find()` followed by `await order.populate('customer')` inside a loop, or Prisma `include` patterns that explode into sequential awaits.

Detection before customers complain

1. Log query counts per request ID in development.

2. Enable slow-query logs (Postgres `log_min_duration_statement`, Mongo profiler).

3. Trace spans around DB clients (OpenTelemetry).

4. Fail CI if a golden endpoint exceeds a query budget (for example max 12 queries).

If you only look at Lighthouse on the marketing site, you will never catch admin API N+1s.

Fixes that actually work

Batch and join

Load parent rows once, collect foreign keys, then `WHERE id IN (...)` or `$in`. Prefer one join or two batched queries over per-row awaits.

DataLoader-style memoization

Per-request caches coalesce duplicate key lookups inside a GraphQL or BFF layer. Do not make DataLoader a global singleton across requests — that leaks data between users.

Projection discipline

Select only fields the response needs. Large documents over the wire amplify every N+1.

Indexing without cargo-cult indexes

Indexes are not free. Each write pays for them. Design from query shapes:

- Equality filters first, then range, then sort keys (compound index order matters).

- Partial indexes for soft-deleted or tenant-scoped hot paths.

- Avoid indexing every column “just in case.”

Always validate with `EXPLAIN (ANALYZE, BUFFERS)` on Postgres or `.explain('executionStats')` on Mongo before celebrating.

Connection pools and hidden saturation

N+1s hold pool slots longer. Under traffic the symptom becomes timeouts, not “slow queries.” Cap pool size intentionally, set statement timeouts, and reject work early with 503s rather than queue forever.

Checklist we run before launch

- Top 10 endpoints have query budgets documented

- Hot filters have covering or compound indexes

- No await-in-loop over DB clients in hot paths

- Staging seeded with production-scale data, not 10 demo rows

- Dashboards alert on p95 latency and DB CPU

Final takeaway

Customers never see your indexes. They feel whether you built them. N+1 elimination and intentional indexing are invisible development work that protects every visible feature.

Frequently asked questions

How do I know if my Node API has N+1 queries?

Count DB round trips per request in logs or APM. If list endpoints scale roughly with row count, you likely have N+1 behavior.

Should I add indexes for every filter?

No. Index query shapes that are frequent and selective. Measure write overhead and use EXPLAIN before shipping indexes.

Do ORMs prevent N+1 problems?

No. ORMs can hide them. You still need batching, includes done correctly, and query budgets.

Is this relevant for Next.js apps?

Yes — Route Handlers, Server Components fetching data, and BFF layers all hit the same database patterns.

Related links

Back to Blog