GraphQL with Node.js

5 questions found

How would you set up a basic GraphQL server in Node.js using Apollo Server, and what are schema and resolvers?

Beginner
A GraphQL server needs a schema (written in GraphQL's Schema Definition Language, describing the available types and operations) and resolvers (functions that actually fetch or compute the data for each field defined in the schema) -- Apollo Server ties these together and handles the HTTP layer, parsing incoming GraphQL queries and invoking the appropriate resolver functions to build the response.
const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type User { id: ID!, name: String!, email: String! }
  type Query { user(id: ID!): User }
`;

const resolvers = {
  Query: {
    user: async (_, { id }) => db.users.findById(id),
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => console.log(`Server ready at ${url}`));
Real-world example A team building a new API for a mobile app chooses GraphQL specifically so the mobile client can request exactly the fields it needs for each screen (like only 'name' and 'avatarUrl' for a list view) in a single request, rather than either over-fetching a full REST resource or needing several separate REST endpoint calls.

Common follow-ups: How does a GraphQL query's shape directly determine which resolvers get called and in what order?;What's the difference between the Query, Mutation, and Subscription root types in a GraphQL schema?

RESTful API Design with Express;Databases & ORMs (MongoDB/Mongoose SQL/Sequelize)

What is the N+1 query problem specific to GraphQL, and how does DataLoader solve it?

Advanced
Because a GraphQL query can request nested related data (like a list of posts, each with their author), a naive resolver implementation ends up calling the database once per post to fetch its author individually, exactly the N+1 problem -- DataLoader solves this by batching and caching individual load requests that occur within the same tick of the event loop, automatically combining what would have been N separate database calls into a single batched query, while still letting each resolver be written as if it were making an independent request.
const DataLoader = require('dataloader');

const userLoader = new DataLoader(async (userIds) => {
  const users = await db.users.findByIds(userIds); // one batched query for all requested IDs
  return userIds.map(id => users.find(u => u.id === id));
});

// Each resolver call looks independent, but DataLoader batches them automatically
const resolvers = {
  Post: { author: (post) => userLoader.load(post.authorId) },
};
Real-world example A GraphQL API serving a query requesting 50 blog posts along with each post's author previously triggered 51 separate database queries under the hood; introducing a DataLoader for user lookups collapses this into just 2 queries total, regardless of how many posts are requested in a single GraphQL query.

Common follow-ups: How does DataLoader's batching window (based on the event loop's microtask queue) actually work to collect individual load() calls together?;Why does DataLoader need to be instantiated fresh per request rather than reused as a singleton across requests?

Databases & ORMs (MongoDB/Mongoose SQL/Sequelize);Performance Optimization & Profiling

How do GraphQL Mutations differ from Queries, and how would you define one to create a new resource?

Intermediate
Queries are for reading data and are expected to have no side effects, while Mutations are explicitly for operations that change server-side state (creating, updating, or deleting data) -- syntactically similar to queries but declared under the schema's Mutation type, with the convention that mutations are executed sequentially (one at a time) when multiple are included in a single request, unlike queries which can be resolved in parallel.
const typeDefs = gql`
  type Mutation { createUser(name: String!, email: String!): User! }
`;

const resolvers = {
  Mutation: {
    createUser: async (_, { name, email }) => {
      return db.users.create({ name, email });
    },
  },
};

// Client sends: mutation { createUser(name: "Alice", email: "alice@example.com") { id name } }
Real-world example A social media application defines a 'createPost' mutation that validates the input, saves the new post to the database, and returns the created post's fields the client requested, following GraphQL's convention of separating this state-changing operation clearly from the app's read-only query operations.

Common follow-ups: Why does GraphQL execute multiple mutations in a single request sequentially rather than in parallel like queries?;How do you handle partial failure when a mutation succeeds in modifying data but a subsequent part of the resolver fails?

Error Handling;Architecture & Design Patterns

How would you implement authentication and authorization in a GraphQL API, given all requests typically go through a single endpoint?

Advanced
Since GraphQL typically exposes just one HTTP endpoint (unlike REST's many distinct routes), authentication is usually handled once via a context function that runs for every request -- extracting and verifying a token, then making the resulting user object available to every resolver via the shared 'context' argument -- while authorization (checking whether the authenticated user is allowed to access a specific field or perform a specific mutation) happens inside individual resolvers, or via a reusable directive applied declaratively in the schema itself.
const server = new ApolloServer({
  typeDefs, resolvers,
  context: async ({ req }) => {
    const token = req.headers.authorization?.replace('Bearer ', '');
    const user = token ? await verifyToken(token) : null;
    return { user };
  },
});

const resolvers = {
  Mutation: {
    deletePost: (_, { id }, { user }) => {
      if (!user || user.role !== 'admin') throw new Error('Forbidden');
      return db.posts.delete(id);
    },
  },
};
Real-world example A GraphQL API verifies the incoming JWT once inside its context function on every request, making the resulting authenticated user object available to all resolvers via context.user, letting individual resolvers implement fine-grained authorization checks (like restricting a deletePost mutation to admins) without each one needing to re-parse or re-verify the token itself.

Common follow-ups: How would a custom '@auth' schema directive centralize authorization logic instead of repeating checks in every resolver individually?;What's the risk of a deeply nested query allowing an unauthorized user to indirectly access restricted data through a related field's resolver?

Authentication & Authorization (JWT OAuth Passport);Security

What is over-fetching and under-fetching in REST APIs, and how does GraphQL specifically address both?

Intermediate
Over-fetching happens when a REST endpoint returns more data than the client actually needs (like a full user object when the client only wanted the name), wasting bandwidth. Under-fetching happens when a single REST endpoint doesn't return enough related data, forcing the client to make several additional round-trip requests to gather everything it needs (like fetching a post, then separately fetching its author, then separately fetching the author's other posts). GraphQL addresses both by letting the client specify exactly which fields and nested relationships it wants in a single request, receiving precisely that shape of data back and nothing more.
# A single GraphQL query fetches exactly what's needed, nested, in one round-trip
query {
  post(id: "1") {
    title
    author { name, avatarUrl }
  }
}
# Compare to REST: GET /posts/1, then GET /users/{authorId}, then GET /users/{authorId}/avatar -- multiple round-trips
Real-world example A mobile app with a limited-bandwidth user base switches its post-detail screen from three separate REST API calls (fetching the post, then the author, then the author's follower count) to a single GraphQL query requesting exactly those three pieces of nested data together, reducing both the number of round-trips and the total bytes transferred.

Common follow-ups: What's the tradeoff GraphQL introduces in exchange for solving over/under-fetching, particularly around caching complexity compared to REST?;How do REST APIs sometimes address the same under-fetching problem using techniques like HTTP/2 server push or embedded/expanded resource query parameters?

HTTP & Web Servers;Performance Optimization & Profiling