LoadForge LogoLoadForge

Strapi Load Testing: How to Performance Test Strapi APIs with LoadForge

Strapi Load Testing: How to Performance Test Strapi APIs with LoadForge

Introduction

Strapi load testing is essential if your CMS powers customer-facing websites, e-commerce storefronts, mobile apps, or headless content APIs. Strapi is popular because it gives teams a flexible way to manage content and expose it through REST or GraphQL APIs, but that flexibility can also create performance challenges when traffic spikes.

If your Strapi instance serves product catalogs, landing pages, blog content, or localized marketing content, slow API responses can quickly become a business problem. A content backend that performs well at low traffic may struggle under concurrent reads, authenticated admin actions, media-heavy responses, or complex filtered queries. That’s why load testing, performance testing, and stress testing your Strapi APIs should be part of your deployment process.

In this guide, you’ll learn how to use LoadForge to performance test Strapi with realistic Locust scripts. We’ll cover basic public content endpoints, authenticated API requests, content creation workflows, and heavier e-commerce CMS scenarios. Along the way, we’ll show how LoadForge’s distributed testing, real-time reporting, cloud-based infrastructure, and CI/CD integration can help you validate that your Strapi environment scales reliably.

Prerequisites

Before you begin load testing Strapi, make sure you have:

  • A running Strapi application, either self-hosted or deployed in the cloud
  • API endpoints exposed through Strapi REST APIs
  • At least one public content type, such as articles, products, or categories
  • A test user account for authenticated requests
  • Optional seeded data so your tests hit realistic content volumes
  • Permission settings configured in Strapi for public and authenticated roles
  • A LoadForge account to run distributed load tests at scale

It also helps to know which version of Strapi you’re running. Modern Strapi APIs often use endpoint patterns like:

  • GET /api/articles
  • GET /api/products?populate=*
  • POST /api/auth/local
  • POST /api/orders
  • GET /api/categories?filters[slug][$eq]=electronics

For performance testing, use a staging or pre-production environment whenever possible. Running stress testing against production can impact editors, customers, and downstream systems like databases, search clusters, and CDNs.

Understanding Strapi Under Load

Strapi is typically used as a headless CMS, meaning frontend applications rely on it for structured content delivery. Under load, Strapi performance depends on several layers:

  • Strapi application server performance
  • Database performance, often PostgreSQL or MySQL
  • Query complexity from filters, sorting, and population
  • Authentication overhead with JWT-based login flows
  • Media and relation population costs
  • Caching behavior in front of Strapi
  • Infrastructure scaling, such as containers, CPU, memory, and connection pooling

Common Strapi bottlenecks

When load testing Strapi, the most common bottlenecks include:

Overuse of populate=*

This is convenient during development, but expensive at scale. Deeply nested relations can dramatically increase response times and database load.

Expensive filters and sorting

Queries like filters[category][slug][$eq]=electronics&sort=publishedAt:desc can become slow if your database indexes are missing or poorly designed.

Authentication bursts

If many users log in simultaneously using /api/auth/local, JWT generation and user lookup can become a hotspot.

Admin-like write traffic

Content creation, updates, and order submissions create heavier database load than read-only CMS traffic.

Large payload responses

Product listings, article feeds, and homepage content with multiple nested relations can produce large JSON responses that increase latency and bandwidth usage.

Media-heavy content delivery

Even if media files are served from object storage or a CDN, Strapi may still spend time resolving media metadata and relationships.

A good Strapi load testing strategy should include both read-heavy and write-heavy scenarios, because real-world traffic often mixes public browsing with authenticated actions.

Writing Your First Load Test

Let’s start with a simple load test for public Strapi content endpoints. This is the most common first step in performance testing a CMS-backed application.

Imagine your Strapi app powers a storefront and blog with these public endpoints:

  • GET /api/articles?pagination[page]=1&pagination[pageSize]=10&sort=publishedAt:desc
  • GET /api/products?pagination[page]=1&pagination[pageSize]=12&populate=thumbnail
  • GET /api/categories

This basic Locust script simulates anonymous users browsing public content.

python
from locust import HttpUser, task, between
 
class StrapiPublicApiUser(HttpUser):
    wait_time = between(1, 3)
 
    @task(3)
    def list_articles(self):
        self.client.get(
            "/api/articles?pagination[page]=1&pagination[pageSize]=10&sort=publishedAt:desc",
            name="/api/articles [list]"
        )
 
    @task(5)
    def list_products(self):
        self.client.get(
            "/api/products?pagination[page]=1&pagination[pageSize]=12&populate=thumbnail",
            name="/api/products [catalog]"
        )
 
    @task(2)
    def list_categories(self):
        self.client.get(
            "/api/categories",
            name="/api/categories"
        )

What this test does

This script models a simple anonymous browsing pattern:

  • Product listing is weighted highest because catalog pages are often the busiest
  • Article listing is moderately frequent
  • Category listing is lower frequency

Why this matters for Strapi

This test helps you answer basic performance testing questions:

  • Can Strapi serve public content quickly under concurrent traffic?
  • Are list endpoints fast enough for frontend page rendering?
  • Does response time degrade as user count increases?
  • Is the database handling repeated listing queries efficiently?

In LoadForge, you can run this script with hundreds or thousands of concurrent users using cloud-based infrastructure and global test locations. That makes it easy to simulate regional traffic bursts without provisioning your own load generators.

Advanced Load Testing Scenarios

A realistic Strapi load test should go beyond simple GET requests. Below are more advanced scenarios that reflect how Strapi is used in production.

Authenticated Strapi API Testing

Many Strapi deployments expose protected endpoints for customer dashboards, partner portals, or internal apps. In this scenario, users log in through Strapi’s local auth endpoint and then retrieve account-specific data.

python
from locust import HttpUser, task, between
import random
 
class StrapiAuthenticatedUser(HttpUser):
    wait_time = between(1, 2)
 
    credentials = [
        {"identifier": "buyer1@example.com", "password": "TestPassword123!"},
        {"identifier": "buyer2@example.com", "password": "TestPassword123!"},
        {"identifier": "editor1@example.com", "password": "TestPassword123!"},
    ]
 
    def on_start(self):
        user = random.choice(self.credentials)
        response = self.client.post(
            "/api/auth/local",
            json={
                "identifier": user["identifier"],
                "password": user["password"]
            },
            name="/api/auth/local [login]"
        )
 
        if response.status_code == 200:
            data = response.json()
            self.token = data["jwt"]
            self.headers = {
                "Authorization": f"Bearer {self.token}"
            }
        else:
            self.token = None
            self.headers = {}
 
    @task(4)
    def get_profile(self):
        if self.token:
            self.client.get(
                "/api/users/me?populate=role",
                headers=self.headers,
                name="/api/users/me"
            )
 
    @task(3)
    def get_saved_orders(self):
        if self.token:
            self.client.get(
                "/api/orders?filters[user][email][$eq]=buyer1@example.com&populate[items][populate]=product",
                headers=self.headers,
                name="/api/orders [user orders]"
            )
 
    @task(2)
    def get_wishlist(self):
        if self.token:
            self.client.get(
                "/api/wishlists?populate[products][populate]=thumbnail",
                headers=self.headers,
                name="/api/wishlists"
            )

What this scenario tests

This script measures:

  • Login performance through /api/auth/local
  • JWT-authenticated request handling
  • Protected content retrieval
  • Relation-heavy queries using populate

Why it’s realistic

Many Strapi-backed e-commerce and CMS applications use authenticated APIs for:

  • Customer accounts
  • Order history
  • Wishlist data
  • Editorial dashboards
  • Membership content

If authentication is slow, users feel it immediately. If protected relation-heavy endpoints are inefficient, account pages may become unusable during traffic spikes.

Load testing tip

For larger tests, use a bigger credential pool and avoid hardcoding a single user filter in order requests. In LoadForge, you can parameterize user data and run distributed performance testing against login flows from multiple regions.

Testing Product Detail, Search, and Filter Queries

Strapi often powers product catalogs where frontend apps request filtered, sorted, and populated content. These are some of the most important endpoints to load test because they closely affect conversion rates.

python
from locust import HttpUser, task, between
import random
 
class StrapiCatalogUser(HttpUser):
    wait_time = between(2, 5)
 
    product_slugs = [
        "wireless-mechanical-keyboard",
        "gaming-mouse-pro",
        "usb-c-docking-station",
        "4k-monitor-27-inch"
    ]
 
    category_slugs = [
        "keyboards",
        "mice",
        "monitors",
        "accessories"
    ]
 
    @task(4)
    def browse_category(self):
        slug = random.choice(self.category_slugs)
        self.client.get(
            f"/api/products?filters[category][slug][$eq]={slug}&pagination[page]=1&pagination[pageSize]=24&sort=publishedAt:desc&populate=thumbnail",
            name="/api/products [category filter]"
        )
 
    @task(3)
    def view_product_detail(self):
        slug = random.choice(self.product_slugs)
        self.client.get(
            f"/api/products?filters[slug][$eq]={slug}&populate[thumbnail]=true&populate[gallery]=true&populate[category]=true&populate[seo]=true",
            name="/api/products [detail by slug]"
        )
 
    @task(2)
    def featured_products(self):
        self.client.get(
            "/api/products?filters[featured][$eq]=true&pagination[pageSize]=8&populate=thumbnail&sort=updatedAt:desc",
            name="/api/products [featured]"
        )
 
    @task(1)
    def homepage_content(self):
        self.client.get(
            "/api/homepage?populate[heroBanners][populate]=image&populate[featuredCategories][populate]=icon&populate[seo]=true",
            name="/api/homepage"
        )

Why this matters

This scenario tests the kinds of API calls that drive:

  • Product listing pages
  • Product detail pages
  • Featured product modules
  • Dynamic homepage content

These endpoints are often more expensive than they first appear because they combine:

  • Filters
  • Sorting
  • Pagination
  • Nested population
  • SEO metadata
  • Media references

If your Strapi APIs are used by a Next.js, Nuxt, React, or mobile frontend, these are exactly the requests you should include in load testing.

Testing Content Creation or Order Submission Workflows

Strapi isn’t just for reading content. Many teams use it for transactional or semi-transactional use cases such as contact forms, quote requests, bookings, or lightweight order capture. Writes are especially important to stress test because they place direct pressure on the database.

In this example, we simulate authenticated users submitting orders to a Strapi collection type.

python
from locust import HttpUser, task, between
import random
import uuid
 
class StrapiOrderSubmissionUser(HttpUser):
    wait_time = between(3, 6)
 
    def on_start(self):
        response = self.client.post(
            "/api/auth/local",
            json={
                "identifier": "buyer1@example.com",
                "password": "TestPassword123!"
            },
            name="/api/auth/local [login]"
        )
 
        if response.status_code == 200:
            self.token = response.json()["jwt"]
            self.headers = {
                "Authorization": f"Bearer {self.token}",
                "Content-Type": "application/json"
            }
        else:
            self.token = None
            self.headers = {}
 
    @task
    def create_order(self):
        if not self.token:
            return
 
        order_number = f"ORD-{uuid.uuid4().hex[:10].upper()}"
 
        payload = {
            "data": {
                "orderNumber": order_number,
                "status": "pending",
                "currency": "USD",
                "subtotal": 249.98,
                "shippingCost": 12.99,
                "taxAmount": 21.25,
                "totalAmount": 284.22,
                "customerEmail": "buyer1@example.com",
                "shippingAddress": {
                    "firstName": "Ava",
                    "lastName": "Turner",
                    "line1": "145 Market Street",
                    "city": "San Francisco",
                    "state": "CA",
                    "postalCode": "94105",
                    "country": "US"
                },
                "items": [
                    {
                        "productSku": "KB-1001",
                        "productName": "Wireless Mechanical Keyboard",
                        "quantity": 1,
                        "unitPrice": 129.99
                    },
                    {
                        "productSku": "MS-2003",
                        "productName": "Gaming Mouse Pro",
                        "quantity": 1,
                        "unitPrice": 119.99
                    }
                ],
                "paymentMethod": "card"
            }
        }
 
        with self.client.post(
            "/api/orders",
            json=payload,
            headers=self.headers,
            name="/api/orders [create]",
            catch_response=True
        ) as response:
            if response.status_code in (200, 201):
                response.success()
            else:
                response.failure(f"Unexpected status code: {response.status_code}")
 
    @task(2)
    def list_recent_orders(self):
        if self.token:
            self.client.get(
                "/api/orders?sort=createdAt:desc&pagination[pageSize]=5",
                headers=self.headers,
                name="/api/orders [recent]"
            )

What this scenario reveals

This test is useful for identifying:

  • Database write latency
  • Validation bottlenecks
  • Slow lifecycle hooks
  • Webhook side effects
  • Lock contention under concurrent writes

If your Strapi project uses custom controllers, lifecycle hooks, or integrations with payment, ERP, or email systems, write-heavy performance testing is especially valuable.

Analyzing Your Results

After running your Strapi load test in LoadForge, focus on a few key metrics.

Response time percentiles

Average response time is useful, but percentiles tell the real story. Pay close attention to:

  • P50 for typical user experience
  • P95 for degraded but still common requests
  • P99 for worst-case latency under load

For Strapi APIs, a low average can hide serious spikes on filtered or populated endpoints.

Requests per second

This helps you understand throughput. Compare endpoint throughput against infrastructure limits and business expectations. For example:

  • Can your catalog endpoints handle traffic from a flash sale?
  • Can homepage content APIs support a major campaign launch?
  • Can authenticated account APIs handle login surges?

Error rate

Look for:

  • 401 or 403 errors from auth misconfiguration
  • 429 errors if rate limiting is enabled
  • 500 errors from application crashes or database issues
  • 502 or 504 errors from reverse proxies or upstream timeouts

Endpoint-level breakdown

In LoadForge’s real-time reporting, review endpoint names you set in Locust, such as:

  • /api/products [catalog]
  • /api/auth/local [login]
  • /api/orders [create]

This makes it easier to pinpoint which Strapi workflows degrade first.

Response size and payload complexity

If endpoints with deep populate options are much slower than simpler requests, that’s a strong sign to reduce response complexity or redesign your content model.

Ramp-up behavior

A Strapi environment may perform well at 50 users and fail at 300. Use LoadForge’s distributed testing and cloud-based infrastructure to ramp traffic gradually and identify the point where latency or error rates begin to climb.

Performance Optimization Tips

Once load testing reveals bottlenecks, these optimizations often help Strapi scale better.

Reduce unnecessary population

Avoid populate=* in production APIs unless absolutely necessary. Request only the fields and relations your frontend actually needs.

Add database indexes

If you frequently filter by fields like:

  • slug
  • publishedAt
  • category
  • featured
  • email

make sure those columns are indexed appropriately in your database.

Paginate aggressively

Do not return huge datasets in a single request. Use pagination for article lists, product catalogs, and category pages.

Cache public content

For public CMS content, use caching layers such as a CDN, reverse proxy, or application cache where appropriate. This is especially effective for homepage and product listing endpoints.

Optimize media delivery

Serve images and static assets from object storage or a CDN instead of routing them inefficiently through your app stack.

Review custom logic

Custom controllers, lifecycle hooks, and plugins can introduce hidden latency. If write endpoints are slow during stress testing, inspect every downstream operation.

Scale infrastructure horizontally

If your Strapi deployment is containerized, horizontal scaling may improve concurrency handling, especially when combined with proper database tuning and connection pooling.

Test continuously

Integrate performance testing into CI/CD so regressions are caught before release. LoadForge supports CI/CD integration, making it easier to validate Strapi performance as schemas, plugins, and frontend traffic patterns evolve.

Common Pitfalls to Avoid

Strapi load testing can produce misleading results if the test design is unrealistic. Avoid these common mistakes.

Testing only public GET endpoints

Strapi applications often include authenticated APIs, admin workflows, and writes. If you only test public reads, you may miss critical bottlenecks.

Using unrealistic payloads

A minimal JSON body may pass, but real users create richer content and more complex requests. Use realistic order, article, or form submission payloads.

Ignoring relation-heavy queries

Endpoints with nested population are often much slower than basic list requests. Include them in your performance testing plan.

Not seeding enough content

A Strapi endpoint with 10 products behaves very differently than one with 100,000 products. Test against realistic data volume.

Reusing one account for all traffic

If every virtual user logs in as the same account, you may create artificial caching or locking behavior. Use a pool of test users where possible.

Forgetting downstream dependencies

Strapi performance can be affected by:

  • Database servers
  • Search services
  • Object storage
  • Email providers
  • Webhooks
  • Reverse proxies

A slow integration may surface as a Strapi API slowdown.

Running a single-user script and calling it load testing

True load testing requires concurrency, ramp-up, sustained traffic, and endpoint mix. LoadForge makes this easier by letting you run large-scale distributed tests from global test locations with real-time reporting.

Conclusion

Strapi is a powerful headless CMS for e-commerce and content-driven applications, but its performance under load depends heavily on your content model, query patterns, authentication flow, and infrastructure. By load testing Strapi APIs with realistic Locust scripts, you can uncover slow endpoints, validate scaling behavior, and prevent CMS bottlenecks from impacting users.

Start with public content endpoints, then expand into authenticated requests, filtered catalog queries, and write-heavy workflows like order submission. Use LoadForge to run distributed load testing at scale, analyze response times in real time, and integrate performance testing into your CI/CD pipeline.

If you want to make sure your Strapi backend is ready for real-world traffic, try LoadForge and start building performance tests that reflect how your application actually works.

Try LoadForge free for 7 days

Set up your first load test in under 2 minutes. No commitment.