Client Area
GraphQL APIsAdvanced

Building GraphQL APIs on DomainIndia: Apollo Server, Hasura, and Best Practices

ByDomain India Team·DomainIndia Engineering
5 min readPublished 21 Apr 2026Updated 23 Jun 2026152 views

In this article

  • 1GraphQL vs REST — when to pick which
  • 2Option A — Apollo Server (Node.js)
  • 3Option B — Hasura (instant GraphQL over PostgreSQL)
  • 4The N+1 problem (and DataLoader fix)
  • 5Authentication patterns

Building GraphQL APIs on DomainIndia: Apollo Server, Hasura, and Best Practices

TL;DR
GraphQL lets clients ask for exactly the fields they need — no more over- or under-fetching. This guide covers when GraphQL beats REST, running Apollo Server (Node.js) or Hasura (instant GraphQL from PostgreSQL) on DomainIndia hosting, and avoiding the "N+1 query" trap.

GraphQL vs REST — when to pick which

FeatureRESTGraphQL
EndpointsMany (/users, /users/:id/posts, etc.)Single (/graphql)
Client flexibilityServer decides response shapeClient picks fields
Over/under-fetchingCommonRare
CachingEasy (HTTP cache)Harder (needs client-side cache like Apollo)
Learning curveLowHigher
Mobile appsFineGreat — thin networks love minimal payloads
Simple CRUDREST is simplerGraphQL is overkill

Pick GraphQL when:

  • Multiple clients (web + iOS + Android) need different fields
  • Deep nested data (user → posts → comments) where REST would need 3+ requests
  • Rapidly evolving frontend — backend doesn't need to deploy new endpoints

Stick with REST when:

  • Simple CRUD
  • HTTP caching is critical
  • Team is small and REST-experienced

Option A — Apollo Server (Node.js)

Full control, custom resolvers, flexible auth.

bash
mkdir gql-api && cd gql-api
npm init -y
npm install @apollo/server graphql @as-integrations/express4 express

server.js:

javascript
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@as-integrations/express4';
import express from 'express';

const typeDefs = `#graphql
  type User { id: ID! name: String! email: String! posts: [Post!]! }
  type Post { id: ID! title: String! author: User! }
  type Query {
    user(id: ID!): User
    posts: [Post!]!
  }
  type Mutation {
    createPost(title: String!, authorId: ID!): Post!
  }
`;

const resolvers = {
  Query: {
    user: async (_, { id }, { db }) => db.user.findUnique({ where: { id } }),
    posts: async (_, __, { db }) => db.post.findMany(),
  },
  User: {
    posts: async (user, _, { db }) => db.post.findMany({ where: { authorId: user.id } }),
  },
  Post: {
    author: async (post, _, { db }) => db.user.findUnique({ where: { id: post.authorId } }),
  },
  Mutation: {
    createPost: async (_, args, { db }) => db.post.create({ data: args }),
  },
};

const apollo = new ApolloServer({ typeDefs, resolvers });
await apollo.start();

const app = express();
app.use('/graphql', express.json(), expressMiddleware(apollo, {
  context: async ({ req }) => ({ db: prismaClient, user: req.user }),
}));

app.listen(4000, () => console.log('http://localhost:4000/graphql'));

Deploy on DomainIndia:

  • Shared cPanel: "Setup Node.js App" — same pattern as any Express API
  • VPS: systemd + nginx reverse proxy (see our Node.js Development articles)

Option B — Hasura (instant GraphQL over PostgreSQL)

Hasura reads your PostgreSQL schema and exposes a full GraphQL API — no resolvers to write.

Install on VPS:

bash
# Docker-based (easiest)
docker run -d -p 8080:8080 
  -e HASURA_GRAPHQL_DATABASE_URL=postgres://user:pass@localhost/mydb 
  -e HASURA_GRAPHQL_ENABLE_CONSOLE=true 
  -e HASURA_GRAPHQL_ADMIN_SECRET=supersecret 
  hasura/graphql-engine:v2.36.0

Visit http://your-vps:8080/console, log in with the admin secret, and every table is now queryable via GraphQL. Permissions, subscriptions (real-time), derived relationships — all via UI.

Insight

Hasura is incredible for internal tools. You save 90% of the boilerplate. For customer-facing APIs, front it with nginx + custom auth + rate limiting.

The N+1 problem (and DataLoader fix)

Classic GraphQL trap. Query:

graphql
{ posts { title author { name } } }

Naive resolver runs:

  • 1 query for all posts
  • 1 query per post to get each author

100 posts = 101 queries. Disaster.

Fix: batch with DataLoader

javascript
import DataLoader from 'dataloader';

const userLoader = new DataLoader(async (ids) => {
  const users = await db.user.findMany({ where: { id: { in: ids } } });
  return ids.map(id => users.find(u => u.id === id));
});

// In resolver:
author: async (post) => userLoader.load(post.authorId)

Now 100 posts = 2 queries (all posts + all authors batched).

Authentication patterns

Three common approaches:

JWT in Authorization header:

javascript
context: async ({ req }) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  const user = token ? jwt.verify(token, JWT_SECRET) : null;
  return { user, db };
}

Session cookie: use express-session middleware before Apollo.

Hasura JWT mode: Hasura validates JWT issued by your auth service (Auth0, Firebase Auth, custom). Claims map to PostgreSQL roles for row-level permissions.

Rate limiting

GraphQL's flexibility lets clients ask for deeply nested data — one query can become expensive. Defend:

Query complexity analysis (graphql-query-complexity):

javascript
import { createComplexityRule } from 'graphql-query-complexity';

plugins: [{
  async requestDidStart() {
    return {
      async didResolveOperation({ request, document }) {
        const complexity = getComplexity({
          schema, query: document,
          variables: request.variables,
          estimators: [simpleEstimator({ defaultComplexity: 1 })],
        });
        if (complexity > 1000) throw new Error(`Query too complex: ${complexity}`);
      },
    };
  }
}]

Depth limiting:

javascript
import depthLimit from 'graphql-depth-limit';
validationRules: [depthLimit(7)]

Common pitfalls

FAQ

Q Can I run GraphQL on shared hosting?

Apollo Server (Node.js) — yes on shared cPanel via "Setup Node.js App". Hasura — no, it needs Docker. Use VPS for Hasura.

Q GraphQL or gRPC for microservices?

gRPC for service-to-service (faster, binary, strict types). GraphQL for client-facing (flexible, introspectable, better DX).

Q Does GraphQL replace REST completely?

No. Use both — GraphQL for complex client queries, REST for simple webhooks / file uploads / health checks.

Q How do I version a GraphQL API?

You don't — you add fields and deprecate old ones (@deprecated(reason: "use newField")). GraphQL's schema evolves continuously without breaking clients.

Q Subscriptions — what VPS size?

4 GB+ for production WebSocket loads. Each connected client uses ~50 KB RAM baseline. 10,000 concurrent = ~500 MB just for connections.

GraphQL servers love the resources of a good VPS. View VPS plans

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket