Performance Testing REST APIs with K6 and Azure Pipelines
REST APIs sit behind mobile apps, web portals, partner integrations and internal services. When response times stretch or error rates rise, customers feel the impact before a dashboard reveals the cause. Performance testing helps teams understand how an API behaves under normal traffic, sudden bursts and sustained demand. Learn more about Nfocussoftwaretesting.com.
k6 provides a developer-friendly way to create repeatable load tests in JavaScript, while Azure Pipelines can run those tests automatically during delivery. Used together, they support a practical performance engineering process for teams working with Microsoft platforms, microservices and cloud-hosted systems.
| Approach | Best fit | Main strength | Common watchpoint |
|---|---|---|---|
| k6 in Azure Pipelines | CI/CD performance checks | Version-controlled, repeatable tests | Requires careful thresholds |
| Manual load testing | Short investigations | Fast exploratory feedback | Difficult to repeat consistently |
| Hosted load platform | Large distributed tests | Scalable traffic generation | Additional cost and setup |
| Application monitoring | Production visibility | Shows real user conditions | Does not replace controlled tests |
Why API Performance Deserves Early Attention
A REST endpoint can appear fast when tested with a single request and still fail under realistic concurrency. Database connection pools, authentication services, queues, third-party calls and container limits all influence the final response. A useful test therefore measures more than average latency: look at p90 and p95 response times, throughput, failed requests and resource saturation.
Start by defining a performance objective for each important endpoint. For example, a search request might need a p95 below 500 milliseconds at 100 requests per second, while a report-generation endpoint may have a longer but clearly defined target. These service-level objectives turn a vague concern about “slowness” into an observable pass or fail condition.
Australian systems often face distinctive traffic patterns. A retailer may experience a sharp evening peak across Sydney and Melbourne, while a government service may receive concentrated demand after a policy announcement. Regional users can also encounter different network conditions from customers in Brisbane, Perth or remote areas, so load generation and monitoring should be interpreted alongside real connectivity data.
Building a Maintainable K6 Workload
Install k6 locally or use a containerised runner, then keep scripts with the application source code. A basic test can import http, issue a request and validate the response with check. Tags such as endpoint, method and scenario make results easier to filter when a test covers several API journeys.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
scenarios: {
steady_load: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '2m', target: 20 },
{ duration: '5m', target: 20 },
{ duration: '2m', target: 0 }
]
}
},
thresholds: {
http_req_failed: ['rate<0.01'],
'http_req_duration{endpoint:search}': ['p(95)<500']
}
};
export default function () {
const response = http.get(`${__ENV.BASE_URL}/api/search?q=testing`, {
tags: { endpoint: 'search' }
});
check(response, {
'returns success': (r) => r.status === 200
});
sleep(1);
}
Use environment variables for base URLs, credentials and test-specific settings rather than committing secrets. Generate test data that resembles production without copying personal information. For authenticated APIs, obtain tokens safely and model token expiry, refresh behaviour and permission checks. A script that sends the same static record repeatedly may test caching rather than the actual service.
API performance work should also reflect the architecture. Teams testing distributed services can combine endpoint checks with contract and integration coverage; microservices testing strategies help clarify which behaviour belongs in a fast pipeline test and which belongs in a broader environment test.
Connecting K6 With Azure Pipelines
Azure Pipelines can execute k6 through a Linux agent, a Docker image or a dedicated task. A simple YAML stage downloads or invokes k6, runs the script and fails the build when thresholds are breached.
- stage: Performance
dependsOn: Build
jobs:
- job: ApiLoadTest
pool:
vmImage: ubuntu-latest
steps:
- script: |
docker run --rm \
-e BASE_URL=$(BASE_URL) \
-v $(Build.SourcesDirectory)/performance:/scripts \
grafana/k6 run /scripts/api-test.js
displayName: Run k6 API test
Store BASE_URL and credentials in variable groups, Azure Key Vault or another approved secret store. Avoid printing request headers, tokens or response bodies in pipeline logs. Use separate configurations for pull requests, integration branches and scheduled runs so a quick smoke test does not create an expensive load event during every commit.
A sensible pipeline may run a small performance smoke test on each relevant change, a moderate baseline test nightly and a larger capacity test before a major release. Schedule heavier runs outside busy hours and notify the owning team in Microsoft Teams or through the existing incident workflow. This is particularly important for shared environments used by several Australian delivery teams working across AEST and AWST.
Designing Workloads That Resemble Users
The quality of the workload matters more than the number of virtual users. Build scenarios around journeys such as login, product search, checkout, account updates or report retrieval. Give each journey a realistic mix of requests and pauses. A system receiving one request every second from each virtual user behaves differently from one receiving a tight burst of ten requests.
Use ramp-up periods to expose capacity limits gradually, then include steady-state and spike scenarios. A soak test lasting several hours can reveal memory leaks, connection exhaustion and gradual queue growth that a short load test misses. For APIs that process files, messages or payments, include payload sizes and data variations representative of actual usage.
Do not let the test environment distort the result. Compare application configuration, database indexes, caching, autoscaling rules and network routes with the intended production setup. If production uses Azure App Service, AKS, Azure SQL or managed Redis, document any material differences in the test report. When a shared test environment is unavoidable, record competing activity so a mid-arvo slowdown is not mistaken for a code regression.
Mobile clients may also shape API demand through retries, offline synchronisation and background refreshes. Teams responsible for both mobile and backend quality can review a mobile automation comparison when deciding how device behaviour should be represented in end-to-end performance scenarios.
Interpreting Results Beyond Average Latency
Average response time hides the users who receive the slowest responses. Review percentile data, especially p90, p95 and p99, alongside request rate and error counts. A healthy average with a deteriorating p99 can indicate a small but important group of requests waiting on a database lock, downstream service or exhausted connection pool.
Correlate k6 output with Azure Monitor, Application Insights, container metrics, database telemetry and gateway logs. Look for timing alignment: did latency increase when CPU reached a limit, when a dependency returned 429 responses or when garbage collection became frequent? Include correlation IDs where possible so a slow API transaction can be traced through its services.
Threshold failures should produce an actionable result rather than a simple red pipeline. Publish the k6 summary as a build artifact, retain trend data and compare the current run with a known baseline. A 10 percent regression in p95 may matter more than an isolated test that barely misses an arbitrary limit. Record test data, commit ID, environment, virtual-user profile and deployment version with every run.
Making Performance Testing Part of Delivery
Performance testing works best as a layered practice. Use lightweight checks to catch obvious regressions early, realistic baseline tests to track capacity and targeted investigations to diagnose bottlenecks. A single large test before release creates late surprises and makes it difficult to identify the change that caused them.
Assign ownership for thresholds and test data. Developers should be able to run a representative script locally or against an ephemeral environment, while testers and platform engineers maintain scenario design, observability and capacity models. For organisations seeking broader quality coverage, nFocus Software Testing supports services including performance testing, automation, Agile and DevOps quality practices.
Treat security and reliability as related concerns. Rate limiting, authentication overhead, payload validation and dependency timeouts should be measured without weakening protective controls. Ensure test traffic is clearly identified, especially when using production-like systems, and confirm that customer notifications, payments and external partners are excluded or safely stubbed.
A well-designed k6 suite running in Azure Pipelines gives teams an evidence-based view of API capacity. Start with a small, repeatable scenario, establish trustworthy thresholds, then expand into soak, spike and capacity testing as the service matures. Build the scripts into your delivery workflow, connect results to observability data and use each run to guide a concrete engineering decision. Engage a specialist testing partner when you need an independent assessment, stronger automation or support scaling performance practices across your delivery teams.