Console Login

Building Production-Ready GraphQL APIs: Performance, Caching, and GDPR Compliance in 2018

Building Production-Ready GraphQL APIs: Performance, Caching, and GDPR Compliance

REST is dying. That might sound aggressive, but if you look at the mobile data consumption metrics coming out of Oslo and Stockholm this year, the verdict is clear. We are sending too much data over the wire. We are making too many round-trips.

GraphQL is the answer everyone is talking about in 2018. But moving from a stateless REST architecture to a graph-based execution engine introduces a new set of infrastructure nightmares that most "managed hosting" providers won't tell you about. They sell you "unlimited bandwidth" but cap your CPU cycles. And GraphQL? It eats CPU for breakfast.

I’ve spent the last month migrating a high-traffic e-commerce backend from a sprawling REST API to Apollo Server 2.0. The results? Payload sizes dropped by 85%. But the CPU load on our previous shared host spiked by 200%. Why? Because parsing query strings and resolving nested fields is computationally expensive.

If you are serious about GraphQL, you need raw iron. You need dedicated cores. You need CoolVDS.

The Architecture: Speed Meets Compliance

For this deployment, we are focusing on a stack that prioritizes low latency for Nordic users and strict adherence to the new GDPR regulations that hit us in May.

  • Runtime: Node.js 10.x (LTS)
  • API Server: Apollo Server 2.0 with Express
  • Database: PostgreSQL 10
  • Caching: Redis 4.0
  • Reverse Proxy: Nginx 1.14
  • Infrastructure: CoolVDS KVM Instance (NVMe Storage)

1. The N+1 Problem: It Will Kill Your Latency

The most common mistake developers make with GraphQL is the N+1 problem. You fetch a list of 50 users, and then for each user, you run a separate database query to fetch their profile settings. That is 51 queries for one request. If your database server is even 5ms away, you just added 250ms of latency.

We solve this with Facebook's dataloader pattern. It batches requests into a single database call.

First, install the package:

npm install dataloader

Here is how you implement it efficiently:

const DataLoader = require('dataloader');
const { db } = require('./db'); // Your knex or pg instance

// Batch function: accepts an array of keys, returns an array of results
const batchUsers = async (ids) => {
  const users = await db('users').whereIn('id', ids);
  // Map back to ensure order matches the keys
  return ids.map(id => users.find(u => u.id === id));
};

const userLoader = new DataLoader(batchUsers);

// Usage in Resolver
const resolvers = {
  Post: {
    author: (post, args, context) => {
      // This waits for the event loop tick to gather all IDs
      return userLoader.load(post.authorId);
    }
  }
};

2. Optimizing Nginx for GraphQL

Unlike REST, GraphQL typically uses a single endpoint (/graphql) via POST requests. This breaks standard caching strategies. You can't just cache the URL. However, you absolutely must compress the JSON responses. GraphQL payloads are text-heavy and compress beautifully.

On a CoolVDS instance, we have full root access to modify nginx.conf. Do not settle for default configs.

Enable Gzip with these specific settings to handle the heavy JSON loads:

http {
    gzip on;
    gzip_disable "msie6";

    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    
    # Don't forget application/json!
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    server {
        listen 80;
        server_name api.yourdomain.no;

        location / {
            proxy_pass http://localhost:4000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }
    }
}

3. Database Tuning on NVMe

If you are hosting in Norway, you are likely targeting customers in Oslo, Bergen, or Trondheim. Latency is king. But disk I/O is the queen. Standard SATA SSDs are fine for static sites, but for a relational database executing complex joins for a GraphQL schema, you want NVMe.

CoolVDS provides local NVMe storage. To take advantage of this high throughput in PostgreSQL 10, we need to tune the random_page_cost.

Open your Postgres config:

sudo nano /etc/postgresql/10/main/postgresql.conf

Change these values. The default Postgres config assumes you are running on spinning rust (HDD):

# Optimized for NVMe Storage
shared_buffers = 2GB            # Approx 25% of RAM
effective_cache_size = 6GB      # Approx 75% of RAM
maintenance_work_mem = 512MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1          # Crucial for NVMe! Default is 4.0
effective_io_concurrency = 200
work_mem = 16MB                 # Be careful raising this too high per connection
Pro Tip: Setting random_page_cost to 1.1 tells the Postgres query planner that seeking random data on disk is almost as cheap as reading sequentially. This is true for NVMe. It encourages the planner to use Index Scans more often, drastically speeding up GraphQL nested queries.

4. Security & GDPR: The Norwegian Context

Since May 25th, GDPR has changed the game. If you are processing data for Norwegian citizens, you need to know where that data lives. Using US-based cloud giants can be legally complex regarding data sovereignty, despite Privacy Shield.

Hosting on a VPS in Norway (or within the EEA) simplifies your compliance posture. With CoolVDS, you know exactly which physical datacenter your data resides in. No hidden replication to a server in Iowa.

Furthermore, you should implement query depth limiting to prevent DoS attacks where a malicious user asks for a infinitely nested query (e.g., author { posts { author { posts ... } } }).

Install graphql-depth-limit:

npm install graphql-depth-limit

Apply it in your Apollo Server setup:

const depthLimit = require('graphql-depth-limit');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(5)], // Fail requests deeper than 5 levels
  context: ({ req }) => {
    // Check auth headers here
    const token = req.headers.authorization || '';
    return { token };
  }
});

Docker Compose for the Full Stack

We don't deploy manually in 2018. We use Docker. Here is a production-ready docker-compose.yml that sets up your Node application alongside Redis for caching query results.

version: '3'
services:
  app:
    build: .
    ports:
      - "4000:4000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://user:pass@db:5432/myapp
      - REDIS_URL=redis://redis:6379
    depends_on:
      - db
      - redis
    restart: always
    
  db:
    image: postgres:10-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=myapp
    restart: always

  redis:
    image: redis:4-alpine
    command: redis-server --appendonly yes
    volumes:
      - redisdata:/data
    restart: always

volumes:
  pgdata:
  redisdata:

Why Infrastructure Matters

GraphQL shifts the bottleneck from the network (latency) to the CPU (processing). In a standard shared hosting environment, your "neighbors"—other websites on the same server—can steal your CPU cycles. This is called the "Noisy Neighbor" effect.

When your CPU is stolen, your GraphQL parser pauses. Your API response time jitters. One request takes 50ms, the next takes 500ms.

CoolVDS uses KVM virtualization. This provides strict resource isolation. If you pay for 4 vCPUs, you get 4 vCPUs. Combined with our local peering at NIX (Norwegian Internet Exchange), your packets stay within Norway, ensuring the lowest possible latency for your local users.

Final Thoughts

Building a GraphQL API in 2018 is powerful, but it exposes weak infrastructure. You can write the cleanest resolvers in the world, but if your database I/O is choked or your CPU is throttled, your users will hate it.

Don't let slow hardware undermine your engineering. Control your stack, tune your Postgres for NVMe, and keep your data compliant.

Ready to see the difference dedicated resources make? Spin up a CoolVDS instance today and benchmark your API response times. The first month is on us if you migrate this week.