15 questions found
What is GraphQL, and how does it differ from a traditional REST API in terms of how clients request data?
Beginner
GraphQL is a query language and runtime for APIs where the client specifies exactly which fields it needs in a single request, and the server returns precisely that shape of data -- unlike REST, where each endpoint returns a fixed data shape (often requiring multiple requests to different endpoints to gather related data, or over-fetching unnecessary fields from a single endpoint), GraphQL exposes a single endpoint with a strongly-typed schema, letting clients compose exactly the query they need.
# GraphQL query: client specifies exactly the fields it wants
query {
user(id: "123") {
name
email
orders { id total }
}
}
# A single request returns exactly this nested shape, no more, no less
Real-world example
A mobile app with limited bandwidth switches a screen from three separate REST API calls (user profile, orders, and preferences) to a single GraphQL query requesting exactly the fields needed for that screen, reducing both the number of round trips and the amount of unused data transferred.
Common follow-ups: What's the specific problem of 'over-fetching' and 'under-fetching' in REST that GraphQL is designed to solve?;Does GraphQL replace REST entirely, or are there scenarios where REST is still the better choice?
RESTful API Design with Express;HTTP & Web Servers
How would you set up a basic GraphQL server in Node.js using Apollo Server, defining a schema and resolvers?
Intermediate
Apollo Server requires a GraphQL schema (defined using the Schema Definition Language, describing available types, queries, and mutations) and a corresponding set of resolver functions that implement how each field in the schema is actually populated -- for a query field, the resolver typically fetches data from a database or another service; the resolver's return value must match the type declared in the schema.
const { ApolloServer, gql } = require('apollo-server-express');
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 });
await server.start();
server.applyMiddleware({ app });
Real-world example
A team building a new API defines their User type and a 'user(id: ID!): User' query in their schema, then implements a resolver that fetches the corresponding user record from their database, giving GraphQL clients a strongly-typed way to query for user data.
Common follow-ups: How does a resolver for a nested field (like a User's list of orders) differ from a top-level Query resolver?;What happens if a resolver's return value doesn't match the type declared in the schema?
RESTful API Design with Express;Databases & ORMs (MongoDB/Mongoose
SQL/Sequelize)
What is the N+1 query problem in GraphQL specifically, and how does DataLoader solve it?
Advanced
Because each field in a GraphQL query has its own independent resolver, a query returning a list of parent objects (like 100 posts) followed by each post's author field naturally triggers a separate resolver call (and potentially a separate database query) for each post's author individually -- resulting in 101 total queries. DataLoader solves this by batching and deduplicating requests made within a single tick of the event loop, collecting all the individual author IDs requested during that tick and issuing a single batched query for all of them at once, then distributing the results back to each individual resolver call.
const DataLoader = require('dataloader');
const userLoader = new DataLoader(async (userIds) => {
const users = await db.users.find({ id: { $in: userIds } }); // single batched query
return userIds.map(id => users.find(u => u.id === id));
});
// Each individual resolver call transparently benefits from batching
const resolvers = {
Post: { author: (post) => userLoader.load(post.authorId) },
};
Real-world example
A social media API's post-listing query, which previously issued 101 separate database queries to resolve 100 posts' individual author fields, drops to just 2 total queries (one for the posts, one batched query for all needed authors) after introducing DataLoader for the author field's resolver.
Common follow-ups: How does DataLoader's batching mechanism actually work using microtask timing to collect requests within a single tick?;What's the difference between DataLoader's batching and caching behaviors, and why does DataLoader typically need to be instantiated fresh per request?
Databases & ORMs (MongoDB/Mongoose
SQL/Sequelize);Performance Optimization & Profiling
What is the difference between a GraphQL Query, Mutation, and Subscription operation type?
Intermediate
A Query fetches data without modifying anything, analogous to a REST GET request. A Mutation performs a write operation that changes data (creating, updating, or deleting a resource), analogous to a REST POST/PUT/DELETE. A Subscription establishes a persistent connection (typically over WebSockets) that pushes real-time updates to the client whenever a specified event occurs on the server, with no direct REST equivalent, since REST has no standard mechanism for server-initiated updates.
type Query { posts: [Post!]! }
type Mutation { createPost(title: String!, body: String!): Post! }
type Subscription { postCreated: Post! }
# Client subscribing to real-time updates
subscription { postCreated { id title } }
Real-world example
A live-blogging application uses a Query to load initial posts on page load, a Mutation for the author to publish a new post, and a Subscription that pushes each newly published post to all currently connected readers' browsers in real time without them needing to refresh.
Common follow-ups: What transport mechanism does a GraphQL Subscription typically use under the hood to push real-time data to clients?;How would you handle authorization differently for a Mutation compared to a read-only Query?
WebSockets & Real-Time Communication;RESTful API Design with Express
How would you implement authentication and authorization in a GraphQL API in Node.js, given there's typically only a single HTTP endpoint?
Advanced
Authentication is typically handled the same way as a REST API -- verifying a JWT or session from the request headers before the GraphQL execution begins, attaching the resulting user object to a shared 'context' object that Apollo Server passes to every resolver -- authorization then happens inside individual resolvers (or via a dedicated directive like @auth applied directly in the schema), checking the authenticated user from context against the specific field or operation being accessed, since GraphQL's single endpoint means you can't rely on route-level middleware the way a REST API might for path-specific authorization.
const server = new ApolloServer({
typeDefs, resolvers,
context: async ({ req }) => {
const user = await verifyToken(req.headers.authorization);
return { user };
},
});
const resolvers = {
Mutation: {
deletePost: (_, { id }, context) => {
if (!context.user || context.user.role !== 'admin') throw new GraphQLError('Forbidden');
return db.posts.delete(id);
},
},
};
Real-world example
A GraphQL API authenticates every incoming request once at the context-creation step, then checks the resulting context.user's role inside each sensitive resolver (like deletePost) individually, since GraphQL's single-endpoint design means authorization can't be scoped by URL path the way it commonly is in a REST API.
Common follow-ups: How would you implement a reusable authorization check that applies consistently across many resolvers without duplicating the check logic everywhere?;What's the tradeoff of field-level authorization (checking inside individual resolvers) versus a schema-level directive-based approach?
Authentication & Authorization (JWT
OAuth
Passport);Security
What is GraphQL introspection, and why might you want to disable it in a production environment?
Intermediate
Introspection lets a client query the GraphQL schema itself (available types, fields, and their descriptions), which is what powers developer tools like GraphiQL/Apollo Sandbox for auto-generating documentation and enabling autocomplete -- while extremely useful during development, exposing the full schema in production can reveal internal implementation details (field names hinting at unreleased features, or the overall data model) to anyone who queries it, which is why many teams disable introspection (and the associated playground UI) specifically in production.
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production', // disabled in production
});
Real-world example
A company discovers a competitor was able to infer an unreleased feature simply by querying their production GraphQL API's introspection endpoint and noticing new, not-yet-documented fields in the schema, prompting them to disable introspection in production going forward.
Common follow-ups: What's the tradeoff of disabling introspection in production if your own frontend team still needs schema information for their own tooling?;Are there ways to expose introspection securely, like only to authenticated internal users?
Security;RESTful API Design with Express
How would you implement query complexity analysis or depth limiting in a GraphQL API to prevent a maliciously deep or expensive query from overwhelming the server?
Advanced
Because GraphQL lets clients compose arbitrarily deep and nested queries (potentially combined with an N+1-triggering pattern across many levels), a malicious or careless client could construct a query that's computationally very expensive to resolve -- query complexity analysis assigns a cost to each field (based on factors like expected result size) and rejects queries exceeding a configured total budget before execution even begins, while depth limiting simply caps how many levels of nesting a query is allowed to have, both providing protection against this specific denial-of-service vector unique to GraphQL's flexible querying model.
const depthLimit = require('graphql-depth-limit');
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const server = new ApolloServer({
typeDefs, resolvers,
validationRules: [depthLimit(5), createComplexityLimitRule(1000)],
});
Real-world example
A public GraphQL API adds a depth limit of 5 and a complexity limit of 1000 after discovering a malicious actor had been submitting deeply nested queries specifically designed to trigger an exponential number of database calls, effectively performing a denial-of-service attack through legitimate-looking GraphQL syntax.
Common follow-ups: How do you determine an appropriate complexity budget without being so restrictive it blocks legitimate, genuinely complex client queries?;What's the relationship between this protection and the N+1 problem DataLoader addresses -- are they solving the same or different issues?
Security;Performance Optimization & Profiling
What is the purpose of GraphQL fragments, and how do they help avoid duplicating field selections across multiple queries?
Intermediate
A fragment defines a reusable, named set of fields on a specific type, which can then be included (spread) into multiple different queries or mutations wherever that same set of fields is needed -- this avoids repeating an identical, potentially large list of fields across every query that needs the same shape of data, and makes it easier to keep field selections consistent when the same data shape (like a summary view of a User) is used in several different places throughout an application.
fragment UserSummary on User {
id
name
avatarUrl
}
query GetPost {
post(id: "1") { title, author { ...UserSummary } }
}
query GetComment {
comment(id: "1") { text, author { ...UserSummary } }
}
Real-world example
A frontend application defines a UserSummary fragment once and reuses it across a dozen different queries that all need to display the same basic author information, ensuring that if the summary fields ever need to change, only the single fragment definition needs updating rather than every individual query.
Common follow-ups: How do GraphQL fragments relate to and support component-based frontend architectures, like colocating a fragment with a specific React component?;What's the difference between a named fragment and an inline fragment (used for querying fields specific to one type in a union or interface)?
Architecture & Design Patterns;RESTful API Design with Express
How would you implement GraphQL schema stitching or federation to compose a single unified GraphQL API from multiple underlying microservices?
Advanced
Apollo Federation lets multiple independent GraphQL services (subgraphs), each owning a specific part of the overall schema, be composed into a single unified graph exposed to clients through a gateway -- a type can even be extended across service boundaries (like a Products service defining a Product type, and a Reviews service adding a 'reviews' field to that same Product type), letting each team independently own and deploy their portion of the schema while clients query the whole thing as if it were one seamless API.
# Products subgraph
type Product @key(fields: "id") { id: ID!, name: String! }
# Reviews subgraph, extending the Product type defined elsewhere
type Product @key(fields: "id") @extends {
id: ID! @external
reviews: [Review!]!
}
Real-world example
A large e-commerce company with separate teams owning Products, Reviews, and Inventory each maintain their own independently deployable GraphQL subgraph, composed via Apollo Federation's gateway into a single unified API that frontend teams query without needing to know or care which underlying microservice actually owns each specific field.
Common follow-ups: How does the federation gateway efficiently plan and execute a query that spans fields owned by multiple different subgraphs?;What's the operational complexity tradeoff of federation compared to a single monolithic GraphQL server for a smaller team?
Microservices Architecture with Node.js;Architecture & Design Patterns
How would you handle file uploads in a GraphQL API, given GraphQL's standard JSON-based transport doesn't naturally support binary file data?
Intermediate
The graphql-upload package (implementing the community-established GraphQL multipart request specification) lets a client send a multipart/form-data request containing both the GraphQL operation and one or more file attachments together, with the server-side resolver receiving the uploaded file as a stream via a special Upload scalar type defined in the schema, bridging the gap between GraphQL's typical JSON transport and binary file data.
const { GraphQLUpload } = require('graphql-upload');
const resolvers = {
Upload: GraphQLUpload,
Mutation: {
uploadAvatar: async (_, { file }) => {
const { createReadStream, filename } = await file;
const stream = createReadStream();
await pipeline(stream, fs.createWriteStream(`./uploads/${filename}`));
return { filename };
},
},
};
Real-world example
A GraphQL API adds an 'uploadAvatar(file: Upload!): User' mutation using graphql-upload, letting clients upload a profile picture through the same GraphQL endpoint used for all other operations, rather than needing a completely separate REST endpoint just for file uploads.
Common follow-ups: Why can't a plain GraphQL query or mutation, sent as standard JSON, include binary file data directly?;What are the security considerations of accepting file uploads through this GraphQL mechanism, similar to the concerns discussed in File Uploads & Media Processing?
File Uploads & Media Processing;HTTP & Web Servers