| name | graphql-implementation |
| description | Design and implement GraphQL APIs with schema design, resolvers, queries, mutations, subscriptions, and best practices. Use when building GraphQL servers, designing schemas, or migrating from REST to GraphQL. |
GraphQL Implementation
Overview
Implement GraphQL APIs with proper schema design, resolver patterns, error handling, and performance optimization for flexible client-server communication.
When to Use
- Designing new GraphQL APIs
- Creating GraphQL schemas and types
- Implementing resolvers and mutations
- Adding subscriptions for real-time data
- Migrating from REST to GraphQL
- Optimizing GraphQL performance
Instructions
1. GraphQL Schema Design
type User {
id: ID!
email: String!
firstName: String!
lastName: String!
role: UserRole!
posts: [Post!]!
createdAt: DateTime!
updatedAt: DateTime!
}
enum UserRole {
ADMIN
USER
MODERATOR
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
publishedAt: DateTime
createdAt: DateTime!
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
post(id: ID!): Post
posts(authorId: ID, limit: Int, offset: Int): [Post!]!
search(query: String!): [SearchResult!]!
}
union SearchResult = User | Post | Comment
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
updatePost(id: ID!, input: UpdatePostInput!): Post!
deletePost(id: ID!): Boolean!
createComment(postId: ID!, text: String!): Comment!
}
input CreateUserInput {
email: String!
firstName: String!
lastName: String!
role: UserRole!
}
input UpdateUserInput {
email: String
firstName: String
lastName: String
role: UserRole
}
input CreatePostInput {
title: String!
content: String!
}
input UpdatePostInput {
title: String
content: String
publishedAt: DateTime
}
type Subscription {
userCreated: User!
postPublished: Post!
commentAdded(postId: ID!): Comment!
}
scalar DateTime
2. Node.js Apollo Server Implementation
const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');
const typeDefs = gql`
type Query {
user(id: ID!): User
users: [User!]!
}
type User {
id: ID!
email: String!
firstName: String!
lastName: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Mutation {
createUser(email: String!, firstName: String!, lastName: String!): User!
createPost(title: String!, content: String!): Post!
}
`;
const resolvers = {
Query: {
user: async (_, { id }, { db }) => {
return db.users.findById(id);
},
users: async (_, __, { db }) => {
return db.users.findAll();
}
},
User: {
posts: async (user, _, { db }) => {
return db.posts.findByAuthorId(user.id);
}
},
Post: {
author: async (post, _, { db }) => {
return db.users.findById(post.authorId);
}
},
Mutation: {
createUser: async (_, { email, firstName, lastName }, { db }) => {
const user = { id: Date.now().toString(), email, firstName, lastName };
db.users.save(user);
return user;
},
createPost: async (_, { title, content }, { user, db }) => {
if (!user) throw new Error('Unauthorized');
const post = { id: Date.now().toString(), title, content, authorId: user.id };
db.posts.save(post);
return post;
}
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({
user: req.user,
db: require('./database')
})
});
const app = express();
server.start().then(() => {
server.applyMiddleware({ app });
app.listen(4000, () => console.log('GraphQL server running on port 4000'));
});
3. Python GraphQL Implementation (Graphene)
import graphene
from datetime import datetime
from typing import List
class UserType(graphene.ObjectType):
id = graphene.ID(required=True)
email = graphene.String(required=True)
first_name = graphene.String(required=True)
last_name = graphene.String(required=True)
posts = graphene.List(lambda: PostType)
class PostType(graphene.ObjectType):
id = graphene.ID(required=True)
title = graphene.String(required=True)
content = graphene.String(required=True)
author = graphene.Field(UserType)
created_at = graphene.DateTime(required=True)
class Query(graphene.ObjectType):
user = graphene.Field(UserType, id=graphene.ID(required=True))
users = graphene.List(UserType)
posts = graphene.List(PostType, author_id=graphene.ID())
def resolve_user(self, info, id):
return User.objects.get(pk=id)
def resolve_users(self, info):
return User.objects.all()
def resolve_posts(self, info, author_id=None):
if author_id:
return Post.objects.filter(author_id=author_id)
return Post.objects.all()
class CreateUserMutation(graphene.Mutation):
class Arguments:
email = graphene.String(required=True)
first_name = graphene.String(required=True)
last_name = graphene.String(required=True)
user = graphene.Field(UserType)
success = graphene.Boolean()
def mutate(self, info, email, first_name, last_name):
user = User.objects.create(
email=email,
first_name=first_name,
last_name=last_name
)
return CreateUserMutation(user=user, success=True)
class Mutation(graphene.ObjectType):
create_user = CreateUserMutation.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
4. Query Examples
# Get user with posts
query GetUserWithPosts {
user(id: "123") {
id
email
firstName
posts {
id
title
createdAt
}
}
}
# Paginated users query
query GetUsers($limit: Int, $offset: Int) {
users(limit: $limit, offset: $offset) {
id
email
firstName
}
}
# Search across types
query Search($query: String!) {
search(query: $query) {
... on User {
id
email
}
... on Post {
id
title
}
}
}
# Create user mutation
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
email
firstName
}
}
# Subscribe to new comments
subscription OnCommentAdded($postId: ID!) {
commentAdded(postId: $postId) {
id
text
author {
firstName
}
}
}
5. Error Handling
const resolvers = {
Query: {
user: async (_, { id }) => {
try {
const user = await User.findById(id);
if (!user) {
throw new GraphQLError('User not found', {
extensions: {
code: 'NOT_FOUND',
userId: id
}
});
}
return user;
} catch (error) {
throw new GraphQLError('Database error', {
originalError: error,
extensions: { code: 'INTERNAL_ERROR' }
});
}
}
}
};
server.formatError = (formattedError) => ({
message: formattedError.message,
code: formattedError.extensions?.code || 'INTERNAL_ERROR',
timestamp: new Date().toISOString()
});
Best Practices
✅ DO
- Use clear, descriptive field names
- Design schemas around client needs
- Implement proper error handling
- Use input types for mutations
- Add subscriptions for real-time data
- Cache resolvers efficiently
- Validate input data
- Use federation for scalability
❌ DON'T
- Over-nest queries deeply
- Expose internal database IDs
- Return sensitive data without authorization
- Create overly complex schemas
- Forget to handle null values
- Ignore N+1 query problems
- Skip error messages
Performance Tips
- Use DataLoader to batch database queries
- Implement query complexity analysis
- Cache at resolver level
- Use connection patterns for pagination
- Monitor query execution time