Skip to content
Plurafy

GraphQL Basics

GraphQL is a query language for APIs that enables clients to request exactly the data they need. Learn to build GraphQL servers and optimize API efficiency.

Build a GraphQL API that provides flexible data querying, reduces over-fetching and under-fetching issues, and enables efficient client-side operations.

REST: Fixed endpoint structure, over/under-fetching GraphQL: Flexible queries, clients request exact data

Schema: Type definitions for data and queries Resolvers: Functions that return field data Queries: Read operations Mutations: Write operations Subscriptions: Real-time updates

const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');
const app = express();
// GraphQL Schema
const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
user(id: ID!): User
users: [User!]!
post(id: ID!): Post
posts: [Post!]!
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String): User
deleteUser(id: ID!): Boolean!
}
`;
// Sample data
const users = [
{ id: '1', name: 'John Doe', email: 'john@example.com' },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com' },
];
const posts = [
{ id: '1', title: 'GraphQL Basics', content: 'Learn GraphQL', authorId: '1' },
{ id: '2', title: 'Node.js Tips', content: 'Node.js best practices', authorId: '2' },
];
// Resolvers
const resolvers = {
Query: {
user: (_, { id }) => users.find(u => u.id === id),
users: () => users,
post: (_, { id }) => posts.find(p => p.id === id),
posts: () => posts,
},
Mutation: {
createUser: (_, { name, email }) => {
const user = { id: String(users.length + 1), name, email };
users.push(user);
return user;
},
updateUser: (_, { id, name }) => {
const user = users.find(u => u.id === id);
if (user && name) user.name = name;
return user;
},
deleteUser: (_, { id }) => {
const index = users.findIndex(u => u.id === id);
if (index !== -1) {
users.splice(index, 1);
return true;
}
return false;
},
},
User: {
posts: (user) => posts.filter(p => p.authorId === user.id),
},
Post: {
author: (post) => users.find(u => u.id === post.authorId),
},
};
// Setup Apollo Server
(async () => {
const server = new ApolloServer({
typeDefs,
resolvers,
});
await server.start();
server.applyMiddleware({ app });
app.listen(4000, () => {
console.log('GraphQL server running on http://localhost:4000/graphql');
});
})();
const { PubSub } = require('apollo-server-express');
const pubsub = new PubSub();
const typeDefs = gql`
type Subscription {
userCreated: User!
postPublished: Post!
}
`;
const resolvers = {
Mutation: {
createUser: async (_, { name, email }) => {
const user = { id: String(users.length + 1), name, email };
users.push(user);
// Publish subscription event
pubsub.publish('USER_CREATED', { userCreated: user });
return user;
},
},
Subscription: {
userCreated: {
subscribe: () => pubsub.asyncIterator(['USER_CREATED']),
},
postPublished: {
subscribe: () => pubsub.asyncIterator(['POST_PUBLISHED']),
},
},
};
const DataLoader = require('dataloader');
// Batch loader function
const userLoader = new DataLoader(async (userIds) => {
// Fetch all users in a single query
const userMap = new Map();
const users = await database.users.findByIds(userIds);
users.forEach(user => userMap.set(user.id, user));
// Return in same order as input
return userIds.map(id => userMap.get(id));
});
const resolvers = {
Post: {
author: (post, _, { userLoader }) => {
return userLoader.load(post.authorId);
},
},
};
// In server setup
const server = new ApolloServer({
typeDefs,
resolvers,
context: () => ({
userLoader,
}),
});
const resolvers = {
Query: {
user: async (_, { id }) => {
try {
const user = await database.users.findById(id);
if (!user) {
throw new Error(`User with ID ${id} not found`);
}
return user;
} catch (error) {
throw new Error(`Failed to fetch user: ${error.message}`);
}
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
formatError: (error) => {
console.error('GraphQL Error:', error);
// Don't expose sensitive error details in production
if (process.env.NODE_ENV === 'production') {
return {
message: 'An error occurred',
extensions: {
code: 'INTERNAL_SERVER_ERROR',
},
};
}
return error;
},
});
const typeDefs = gql`
input CreateUserInput {
name: String!
email: String!
}
input UpdateUserInput {
name: String
email: String
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User
}
`;
const resolvers = {
Mutation: {
createUser: async (_, { input }, context) => {
// Validate input
if (!input.email.includes('@')) {
throw new Error('Invalid email format');
}
if (input.name.length < 2) {
throw new Error('Name must be at least 2 characters');
}
const user = await database.users.create(input);
return user;
},
updateUser: async (_, { id, input }) => {
const user = await database.users.findById(id);
if (!user) {
throw new Error(`User ${id} not found`);
}
Object.assign(user, input);
await user.save();
return user;
},
},
};
const typeDefs = gql`
directive @auth(roles: [String!]!) on FIELD_DEFINITION
directive @deprecated(reason: String) on FIELD_DEFINITION
type User {
id: ID!
name: String!
email: String!
admin: Boolean @auth(roles: ["ADMIN"])
}
type Query {
users: [User!]! @auth(roles: ["ADMIN", "MODERATOR"])
me: User!
}
`;
const authDirective = (schema) => {
return mapSchema(schema, {
'MapperKind.OBJECT_FIELD': (fieldConfig) => {
const authDirectiveArgs = getDirectiveValues(authDirective, fieldConfig);
if (authDirectiveArgs) {
const { resolve } = fieldConfig;
fieldConfig.resolve = (obj, args, context, info) => {
if (!context.user) {
throw new Error('Unauthorized');
}
const userRoles = context.user.roles || [];
const hasRole = authDirectiveArgs.roles.some(role =>
userRoles.includes(role)
);
if (!hasRole) {
throw new Error('Insufficient permissions');
}
return resolve ? resolve(obj, args, context, info) : obj[info.fieldName];
};
}
return fieldConfig;
},
});
};
  1. Schema Design

    • Clear, descriptive field names
    • Proper nullable/non-nullable types
    • Reusable fragments and interfaces
  2. Performance

    • Use DataLoader for batch queries
    • Implement query complexity analysis
    • Cache frequently accessed data
    • Monitor query depth
  3. Security

    • Authenticate all mutations
    • Validate input thoroughly
    • Limit query complexity
    • Hide sensitive information
  4. Developer Experience

    • Comprehensive error messages
    • Introspection for tooling
    • Documentation in schema
    • Example queries
  5. Monitoring

    • Log all operations
    • Track query performance
    • Monitor resolver execution time
    • Alert on anomalies
  • Mobile Apps: Bandwidth-efficient queries
  • Real-Time Apps: Subscriptions for updates
  • Complex Data Fetching: Flexible queries reduce requests
  • Internal Tools: Self-documenting APIs
  • Microservices: API gateway and federation