What this usually means
A slow API usually has one dominant bottleneck. It is rarely the framework or language — it is almost always the database, an external API call, or a CPU-bound operation happening in the request path. The most common pattern: an N+1 query where one database query fetches a list, and then a separate query runs for every item in that list. The code looks fine because each individual query is fast, but 1 query becomes 1 + 100 = 101 queries, and the latency multiplies.
The first ten minutes \u2014 establish facts before touching code.
- 1Add request timing logs. Log the duration of each major step: auth, validation, database query, external API call, serialisation. Find the step that takes the most time.
- 2Check the number of database queries per request. Enable query logging or use an APM tool. If a single request makes 50+ queries, you have an N+1 problem.
- 3Check for missing database indexes. A query that scans a full table is fast on 100 rows but slow on 100,000 rows.
- 4Check if the slowdown correlates with data growth. Run the same request on a smaller dataset to isolate data-volume issues.
- 5Check if a synchronous external API call is in the critical path. An external service that takes 2 seconds makes your API take 2+ seconds.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchApplication performance monitoring (APM) — Datadog, New Relic, Sentry Performance, or OpenTelemetry traces
- searchDatabase query logs — enable slow query logging, check query execution plans with EXPLAIN
- searchORM query logging — enable verbose logging to see every query issued per request
- searchCode — the request handler, service layer, and data access layer
- searchExternal API integrations — are they called synchronously in the request path?
- searchServer metrics — CPU, memory, and database connection pool utilisation during slow requests
Practical causes, not theory. These are the things you will actually find.
- warningN+1 query: one query fetches a list, then a query per item fetches related data
- warningMissing database index: query scans the entire table instead of using an index
- warningSynchronous external API call in the request path
- warningORM lazy-loading triggers hidden queries when accessing relations
- warningLarge result set serialised to JSON synchronously
- warningDatabase connection pool exhausted — requests queue waiting for a connection
- warningInefficient query: fetching all columns (SELECT *) when only a few are needed
Concrete fix directions. Pick the one that matches your root cause.
- buildUse eager loading to fetch related data in a single query with JOINs or IN clauses instead of N+1 queries
- buildAdd database indexes on columns used in WHERE, JOIN, and ORDER BY clauses
- buildMove slow external API calls to a background job — return immediately and notify the user when complete
- buildPaginate large result sets — never return all rows in a single response
- buildAdd a response cache for data that changes infrequently
- buildUse `SELECT` with specific columns instead of `SELECT *` to reduce data transfer
A fix you cannot prove is a guess. Close the loop.
- verifiedRun the slow request with query logging enabled. Compare the query count before and after the fix.
- verifiedUse EXPLAIN ANALYSE on the slow query. Confirm it uses the expected index and scans fewer rows.
- verifiedBenchmark the endpoint: measure p50, p95, and p99 latency before and after the fix.
- verifiedRun a load test to ensure the fix holds under concurrent traffic.
- verifiedSet up latency monitoring and alerting to catch regressions.
Things that make this bug worse or harder to find.
- warningAdding a cache without fixing the underlying slow query
- warningIncreasing the server timeout instead of reducing response time
- warningLoading all data and filtering in application code instead of in the database query
- warningNot checking for N+1 queries during code review — they are easy to miss and expensive to fix later
- warningAssuming the database is the bottleneck without measuring — sometimes it is the application code