Every founder has a version of the same nightmare. The launch email goes out, the first wave of users arrives, and the app slows to a crawl. Pages time out, checkout fails, and the support inbox fills up. The worst part is that the problem was almost always findable days earlier. A few hours of structured load testing before launch would have shown exactly where the system bends.
This guide walks through a practical approach to load testing web apps using k6, an open source tool that lets you write tests in JavaScript. You do not need a dedicated performance team. You need clear goals, realistic scenarios, and the discipline to read the results honestly.
Functional tests tell you whether a feature works for one user. They say nothing about what happens when hundreds of people use it at once. Problems such as exhausted database connections, slow queries that only hurt under contention, and memory leaks in long-running processes simply do not show up in a normal test suite.
Fixing these issues after launch is expensive in every sense. You are debugging under pressure, with real customers watching, and every patch risks making things worse. Finding them in a controlled test means you can fix them calmly. If your team already tracks speed in CI, pairing this work with performance budgets for web apps gives you both a front-end and a back-end safety net.
A load test without targets is just a fancy way of generating logs. Before writing any script, agree on measurable goals. These are usually expressed as service level objectives, for example:
The exact numbers depend on your product. A checkout flow deserves stricter limits than an internal report page. Write them down, because they become the pass or fail criteria for every future test.
The most common mistake is hammering a single endpoint with identical requests. Real traffic is a mix. Some users browse, some search, a few check out, and many just load the home page. Pull this mix from analytics if you have it, or from a sensible estimate if you do not.
A good scenario list for an e-commerce style app might look like this:
Include realistic pauses between actions, called think time. Without them, each virtual user behaves like a robot and your test produces far more load per user than real life ever would, which makes the results hard to interpret.
k6 scripts are plain JavaScript. A minimal test defines how many virtual users to run, for how long, and what thresholds count as a pass. A typical structure has three parts: options (stages and thresholds), a default function (what each virtual user does), and optional setup for test data.
In the options block, define stages that ramp up gradually, hold at peak, and ramp down. A gradual ramp matters because it lets you see at which load level the response times start to climb. A sudden jump to peak hides that information and can trigger autoscaling lag that is not representative of normal growth.
Attach thresholds to your goals from Step 1, such as a limit on the 95th percentile request duration and a cap on the failed request rate. When a threshold is breached, k6 exits with a failure code, which is exactly what you need to fail a CI job automatically.
Results are only as trustworthy as the environment they come from. Before running anything serious, check these basics:
Run the load generator from a machine that is not itself the bottleneck. A laptop on office wifi can saturate its own network long before your server breaks a sweat.
Consider an illustrative case. A startup is preparing to launch a booking platform backed by a marketing push. Its team expects that, on launch day, a few hundred people might use the app at the same time, with most of them searching for availability and a smaller group completing payments.
The team writes a k6 test with three scenarios weighted to match the expected mix, ramps up over ten minutes, and holds the peak for twenty. In the first run, response times look healthy until the load reaches a certain level. Then the search endpoint begins to slow down sharply while CPU on the application servers stays low. That pattern points away from compute and toward the database.
Looking at slow query logs, the team finds that the availability search runs a query without a proper index, and each request holds a database connection for far too long. Under load, the connection pool empties and requests queue up. After adding an index and a short-lived cache, the same test passes its thresholds with room to spare. If you are facing a similar pattern, our walkthrough on fixing slow database queries with indexing and tuning covers the diagnosis in detail.
The point is not the specific fix. It is that the problem surfaced on a Tuesday afternoon in a test run, not on launch morning in front of customers.
Averages hide pain. If most requests take 100 milliseconds and a few take ten seconds, the average looks fine while a real group of users has a terrible experience. Focus on percentiles, particularly the 95th and 99th, and look at the distribution over time rather than one summary number.
Correlate k6 output with server-side data. Useful questions include:
Good observability makes this far easier, so make sure tracing and metrics are in place before the test begins.
Moving slow work off the request path is often the single biggest win, and a proper queue is usually the answer. We cover the options in background jobs and queues for web apps.
A load test does not prove your system is perfect. It tells you where it will fail first, which is the most useful thing to know before real customers arrive.
The teams that benefit most treat load testing as routine, not a one-off event. Keep your scripts in the same repository as your application code. Run a short version on every release candidate, and a full version before big campaigns, new regions or architecture changes. Pair it with a reliable end-to-end suite and gradual rollouts so that correctness and capacity are both protected on every release.
Load testing is one of the highest value activities a team can do in the final weeks before launch. It turns vague worry into concrete findings: this endpoint slows down here, this pool runs dry there, this cache needs a guard. With k6, clear targets and realistic scenarios, even a small team can get meaningful answers in a few days.
If you would like help preparing a product for launch, from architecture reviews to performance testing and hardening, explore our web development services or see how we approach SaaS development for products that need to scale from day one.