//Technology

REST vs. GraphQL: Scaling Express APIs vs. Apollo Under Heavy Load

Scaling Express REST vs Apollo GraphQL on Indian VPS: compare architecture, caching, DB load, horizontal scaling, and observability for high‑traffic Node.js APIs.

6 min read
REST vs. GraphQL: Scaling Express APIs vs. Apollo Under Heavy Load

When you build a web service that must handle thousands of requests per second, the choice of API architecture is a critical decision. Two approaches dominate the conversation: classic REST, often implemented with Express.js, and GraphQL, commonly served through Apollo Server. Both run on Node.js, but they expose data, manage queries, and scale differently. This article walks through the practical considerations of scaling an Express REST API versus an Apollo GraphQL server, focusing on performance patterns, resource usage, and operational tooling that matter to Indian developers running cloud VPS or dedicated servers.

1. Core Architectural Differences

REST (Representational State Transfer) is a resource-oriented model. Each endpoint represents a collection or a single entity (e.g., /users, /orders/123). The server decides which fields to return, and the client often makes multiple round-trips to gather related data.

GraphQL uses a single endpoint (typically /graphql) that accepts a query describing exactly what data the client needs. The server resolves the query by invoking resolvers, which can aggregate data from multiple sources in one request.

These differences influence how you design caching, connection handling, and load distribution, especially during traffic spikes.

2. Request-Handling Efficiency

2.1 Data Over-fetching vs. Under-fetching

  • REST: Endpoints often return fixed payloads. Clients may receive more data than they need (over-fetching) or must issue extra calls for related resources (under-fetching).
  • GraphQL: Clients request only the fields they need, reducing payload size. However, complex queries can trigger many resolver functions, increasing CPU load.

2.2 Connection Management

Both Express and Apollo run on Node.js’s event loop, which handles many concurrent connections efficiently. The real distinction appears in how you configure the HTTP server and the process manager.

Below are sample steps to set up a production-ready process manager (PM2) and a reverse proxy (Nginx) on two common Linux families. Adjust the version numbers to match your OS.

Debian/Ubuntu (apt)

# Install Nginx
sudo apt update && sudo apt install -y nginx

# Install Node.js (LTS) and npm
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs

# Install PM2 globally
sudo npm install -g pm2

# Create a systemd service for PM2 (keeps your apps alive across reboots)
pm2 startup systemd -u $USER --hp $HOME

AlmaLinux/Rocky/RHEL (dnf)

# Install Nginx
sudo dnf install -y epel-release
sudo dnf install -y nginx

# Install Node.js (LTS) from Nodesource
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash -
sudo dnf install -y nodejs

# Install PM2 globally
sudo npm install -g pm2

# Enable PM2 at boot
pm2 startup systemd -u $USER --hp $HOME

These commands are identical for a REST or GraphQL service; the difference lies in how you configure the application itself.

3. Caching Strategies

3.1 HTTP-Level Caching for REST

Because each REST endpoint returns a predictable payload, you can use standard HTTP cache headers (Cache-Control, ETag) and Nginx’s proxy_cache module. Example Nginx snippet for a read-only /products endpoint:

location /products {
    proxy_pass http://localhost:3000;
    proxy_set_header Host $host;
    proxy_cache my_cache;
    proxy_cache_valid 200 10m;
    add_header X-Cache $upstream_cache_status;
}

3.2 Response-Level Caching for GraphQL

GraphQL responses vary per query, making generic HTTP caching less effective. Instead, you can:

  • Use Apollo Server’s built-in response cache (requires a cache store like Redis).
  • Implement persisted queries so identical queries map to the same cache key.
  • Leverage field-level caching inside resolvers (e.g., memoizing database calls).

Below is a minimal Redis-backed cache setup for Apollo Server (Node.js code, not OS-specific):

const { ApolloServer } = require('apollo-server-express');
const { RedisCache } = require('apollo-server-cache-redis');

const cache = new RedisCache({
  host: '127.0.0.1',
  port: 6379,
});

const server = new ApolloServer({
  typeDefs,
  resolvers,
  cache,
  persistedQueries: {
    ttl: 300, // cache persisted queries for 5 minutes
  },
});

4. Database Load and Query Optimization

Both architectures eventually hit a database (MySQL, PostgreSQL, MongoDB, etc.). The key is to keep the number of round-trips low.

  • REST: Use JOIN queries or aggregation pipelines to fetch related data in one call. Batch endpoints (e.g., /orders?ids=1,2,3) reduce request count.
  • GraphQL: Apply DataLoader to batch and cache resolver calls within a single request, preventing N+1 query problems.

Example DataLoader setup (Node.js):

const DataLoader = require('dataloader');
const db = require('./db');

const userLoader = new DataLoader(async (ids) => {
  const rows = await db.query('SELECT * FROM users WHERE id IN (?)', [ids]);
  const userMap = new Map(rows.map(row => [row.id, row]));
  return ids.map(id => userMap.get(id));
});

5. Scaling Under High Traffic

5.1 Horizontal Scaling with Load Balancers

Regardless of API style, you’ll eventually need multiple Node.js instances behind a load balancer. In Indian data centers, AtoZNode offers both software load balancers (HAProxy) and cloud-native options. The configuration is identical for Express and Apollo:

Debian/Ubuntu (apt)

# Install HAProxy
sudo apt install -y haproxy

# Basic round-robin config (edit /etc/haproxy/haproxy.cfg)
frontend http_in
    bind *:80
    default_backend nodes

backend nodes
    balance roundrobin
    server node1 127.0.0.1:3001 check
    server node2 127.0.0.1:3002 check
    server node3 127.0.0.1:3003 check

AlmaLinux/Rocky/RHEL (dnf)

# Install HAProxy
sudo dnf install -y haproxy

# Edit /etc/haproxy/haproxy.cfg similarly to the block above

After editing, restart HAProxy:

sudo systemctl restart haproxy

5.2 Autoscaling Considerations

When traffic spikes unpredictably (e.g., during a product launch), you can combine PM2’s cluster_mode with horizontal scaling. PM2 can spawn a worker per CPU core, maximizing Node.js’s single-threaded nature.

# Start an Express or Apollo app in cluster mode
pm2 start index.js -i max   # “max” creates one process per available CPU

Monitor CPU and memory with pm2 monit and set up alerts in your VPS control panel.

6. Observability and Debugging

Both stacks benefit from structured logging, request tracing, and metrics.

  • Logging: Use Winston or Pino for JSON logs that can be shipped to a central log aggregator.
  • Tracing: OpenTelemetry works with Express and Apollo. Export traces to Jaeger or a cloud-based APM.
  • Metrics: Expose Prometheus metrics via express-prom-bundle for REST or apollo-server-plugin-base for GraphQL.

Sample Prometheus endpoint for an Express app:

const promBundle = require('express-prom-bundle');
app.use(promBundle({ includeMethod: true }));

For Apollo, you can add a simple plugin:

const { ApolloServerPluginLandingPageGraphQLPlayground } = require('apollo-server-core');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [ApolloServerPluginLandingPageGraphQLPlayground()],
});

Conclusion

Choosing between a traditional Express REST API and an Apollo GraphQL server depends on the shape of your data and expected traffic patterns. REST offers straightforward caching and predictable payloads, which can be easier to scale with standard HTTP tools. GraphQL reduces over-fetching and consolidates multiple data needs into a single request, but it requires careful resolver design, response-level caching, and often a Redis or similar store.

From an operations standpoint, both can be hardened using the same Linux tooling: Nginx or HAProxy for load balancing, PM2 for process management, and Redis for optional caching. By applying the best-practice steps outlined above—optimizing database access, employing appropriate caching, and instrumenting observability—you can run either architecture at scale on AtoZNode’s VPS or dedicated servers, serving Indian users with reliability and performance.

scaling nodejs apiexpress restapollo graphqlnginx reverse proxypm2 process managerredis cachinghaproxy load balancingobservability prometheus

Try it on your own server

Follow along on a Cloud VPS with full root access, or read the step-by-step knowledge base guides.