GraphQL Queries & Resolvers Explained: Arguments, Context & Data Fetching
Resolvers are the functions that populate each field in a GraphQL response — they connect the schema to databases, REST APIs, and other data sources.
What You’ll Learn
- How resolvers map schema fields to data
- Query arguments and type-specific resolvers
- Context for authentication and database access
- The resolver chain and parent arguments
- DataLoader for batching and caching
- Pagination with cursors
Why Resolvers Matter
The schema defines what data exists, but resolvers define how to get it. Without resolvers, a GraphQL schema is just documentation. DodaTech’s Durga Antivirus Pro dashboard uses resolvers to fetch device data from Firestore, threat intelligence from an external API, and user profiles from Auth0 — all combined in a single GraphQL response.
sequenceDiagram
participant Client
participant GraphQL
participant Resolver
participant DB
participant REST
Client->>GraphQL: query { device(id:"1") { name threats { name } } }
GraphQL->>Resolver: device(parent, {id:"1"}, context)
Resolver->>DB: SELECT * FROM devices WHERE id = '1'
DB-->>Resolver: {id:"1", name:"Office-PC", userId:"u1"}
Resolver-->>GraphQL: {name:"Office-PC"}
GraphQL->>Resolver: Device.threats(parent={name:"Office-PC", userId:"u1"})
Resolver->>REST: GET /threats?userId=u1
REST-->>Resolver: [{name:"Emotet"}]
Resolver-->>GraphQL: [{name:"Emotet"}]
GraphQL-->>Client: {data: {device: {name:"Office-PC", threats:[{name:"Emotet"}]}}}
Resolver Function Signature
Every resolver receives four arguments:
const resolvers = {
Query: {
device: (parent, args, context, info) => {
// parent: result of parent resolver (null for Query root)
// args: { id: "1" }
// context: { db, auth, dataSources }
// info: field selection, path, operation details
return context.db.devices.find(d => d.id === args.id);
},
},
};Resolver Chain
GraphQL resolves queries from top to bottom. Each field’s resolver can return data that becomes the parent for child fields:
const resolvers = {
Query: {
threats: () => threatsDb, // Returns all threats
},
Threat: {
// device is a field on Threat type
device: (parent, args, context) => {
// parent is the Threat object from the threats resolver
return context.db.devices.find(d => d.id === parent.deviceId);
},
},
};You might be wondering: “Do I need a resolver for every field?” No — if a field doesn’t have a resolver, GraphQL uses the default resolver which looks for a property with the same name on the parent object.
Context
Context is shared across all resolvers in a request. It’s the right place for authentication, database connections, and DataLoader instances:
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
// Verify JWT token from request headers
const token = req.headers.authorization?.split(' ')[1];
let user = null;
if (token) {
try {
user = await verifyToken(token);
} catch (e) {
// Invalid token — user stays null
}
}
return {
user, // Authenticated user or null
db, // Database client
dataSources: { // DataLoader instances
deviceLoader: createDeviceLoader(),
threatLoader: createThreatLoader(),
},
};
},
});Resolvers access context via the third argument:
const resolvers = {
Query: {
myDevices: (parent, args, context) => {
if (!context.user) {
throw new AuthenticationError('Not authenticated');
}
return context.db.devices.findByUserId(context.user.id);
},
},
};DataLoader for N+1 Problem
The N+1 problem: fetching 100 devices and their threats makes 1 query for devices + 100 queries for threats = 101 queries.
const DataLoader = require('dataloader');
// Batch function — receives array of keys
async function batchThreatsByDeviceIds(deviceIds) {
const threats = await db.threats.findByDeviceIds(deviceIds);
// Must return results in the same order as deviceIds
return deviceIds.map(id =>
threats.filter(t => t.deviceId === id)
);
}
// Create loader in context
const context = {
threatLoader: new DataLoader(batchThreatsByDeviceIds),
};
// In resolver — uses loader instead of direct query
const resolvers = {
Device: {
threats: (parent, args, context) => {
// Single call per device — DataLoader batches them
return context.threatLoader.load(parent.id);
},
},
};Now 100 devices + their threats = 1 query for devices + 1 batch query for threats = 2 queries total.
Pagination with Connections
The Relay Connection spec provides cursor-based pagination:
type DeviceConnection {
edges: [DeviceEdge!]!
pageInfo: PageInfo!
}
type DeviceEdge {
cursor: String!
node: Device!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
devices(first: Int!, after: String): DeviceConnection!
}const resolvers = {
Query: {
devices: async (parent, { first, after }, context) => {
const afterIndex = after
? Buffer.from(after, 'base64').toString().indexOf(':')
: -1;
const allDevices = await context.db.devices.findAll();
const startIndex = afterIndex >= 0 ? afterIndex + 1 : 0;
const page = allDevices.slice(startIndex, startIndex + first);
return {
edges: page.map(device => ({
cursor: Buffer.from(`device:${device.id}`).toString('base64'),
node: device,
})),
pageInfo: {
hasNextPage: startIndex + first < allDevices.length,
hasPreviousPage: startIndex > 0,
startCursor: page[0] ? Buffer.from(`device:${page[0].id}`).toString('base64') : null,
endCursor: page[page.length - 1] ? Buffer.from(`device:${page[page.length - 1].id}`).toString('base64') : null,
},
};
},
},
};Common Mistakes
1. Not Using DataLoader
The N+1 problem causes performance degradation that worsens linearly with data size. Always use DataLoader for related data.
2. Putting Expensive Operations in Context
Context is created once per request. Don’t put expensive initialization there — it runs even for simple queries.
3. Throwing Generic Errors
Use ApolloError subclasses: AuthenticationError, ForbiddenError, UserInputError, ValidationError.
4. Over-fetching in Resolvers
If the client only requests device.name, don’t fetch all threat data in the same resolver. Let child resolvers handle their own data.
Practice Questions
- What are the four resolver arguments and their purposes?
- How does the resolver chain work for nested queries?
- What problem does DataLoader solve?
- What is the default resolver behavior?
- How does context differ from arguments?
Answers:
parent(parent resolver result),args(query arguments),context(shared state),info(execution details).- Each parent resolver’s return becomes the
parentfor child field resolvers. GraphQL resolves fields depth-first, so a parent always resolves before its children. - N+1 query problem — fetching parent items and their children separately causes N+1 database queries. DataLoader batches and caches child queries.
- If no resolver is defined, GraphQL looks for a property with the field name on the parent object.
- Context is shared across all resolvers in a request (auth, DB connections). Arguments are specific to one resolver call.
Challenge: Write resolvers for Durga Antivirus Pro that: (1) fetch a user from the database, (2) batch-load their devices using DataLoader, (3) if the user is an admin, include all devices; otherwise only the user’s own devices, and (4) paginate devices using cursor-based pagination.
FAQ
Try It Yourself
// A resolver with DataLoader and auth
const resolvers = {
Query: {
userDevices: async (parent, args, context) => {
if (!context.user) throw new AuthenticationError('Login required');
return context.deviceLoader.load(context.user.id);
},
},
Device: {
threats: (parent, args, context) =>
context.threatLoader.load(parent.id),
},
};What’s Next
| Topic | Description |
|---|---|
| Mutations Guide | Creating, updating, and deleting data |
| GraphQL Architecture | Server config, subscriptions, federation |
| Types & Schema Design | Schema-first design and type system |
| RESTful APIs | Compare resolver patterns with REST endpoints |
What’s Next
Congratulations on completing this Graphql Queries Resolvers tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro