Implementation:
-
Interface and union types for polymorphic data:
interface Product {
id: ID!
name: String!
price: Float!
}
type Book implements Product {
id: ID!
name: String!
price: Float!
author: String!
pages: Int!
}
union SearchResult = Book | Author | Publisher
-
Schema stitching for microservices:
const { stitchSchemas } = require('@graphql-tools/stitch');
const gatewaySchema = stitchSchemas({
subschemas: [
{
schema: await loadRemoteSchema('http://products-service/graphql'),
merge: {
Product: {
selectionSet: '{ id }',
fieldName: 'product',
args: (originalResult) => ({ id: originalResult.id }),
}
}
}
]
});
Example:
Extending types across services:
extend type Product {
reviews: [Review!]
averageRating: Float
}
Implementation:
-
DataLoader batching:
const userLoader = new DataLoader(async (userIds) => {
const users = await User.find({ _id: { $in: userIds } });
return userIds.map(id =>
users.find(u => u._id.equals(id))
);
});
const resolvers = {
Post: {
author: (post) => userLoader.load(post.authorId)
}
};
-
Query cost analysis:
const { createComplexityRule } = require('graphql-query-complexity');
const rule = createComplexityRule({
maximumComplexity: 1000,
variables: {},
onComplete: (complexity) => console.log('Query complexity:', complexity),
createError: (max, actual) => new Error(`Query too complex: ${actual}/${max}`)
});
Example:
Persisted queries implementation:
const { InMemoryLRUCache } = require('@apollo/utils.keyvaluecache');
const { PersistedQueryNotSupportedError } = require('apollo-server-errors');
const server = new ApolloServer({
cache: new InMemoryLRUCache(),
plugins: [{
async requestDidStart() {
return {
async didResolveOperation({ request, document }) {
if (!request.query && !request.extensions?.persistedQuery) {
throw new PersistedQueryNotSupportedError();
}
}
};
}
}]
});
Implementation:
-
JWT authentication middleware:
const context = ({ req }) => {
const token = req.headers.authorization || '';
try {
const user = jwt.verify(token.replace('Bearer ', ''), process.env.JWT_SECRET);
return { user };
} catch (err) {
return {};
}
};
-
Schema directives for authorization:
class AuthDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;
field.resolve = async function (...args) {
const [ , , context ] = args;
if (!context.user) {
throw new AuthenticationError('Not authenticated');
}
return resolve.apply(this, args);
};
}
}
Example:
Role-based access control:
directive @hasRole(role: UserRole!) on FIELD_DEFINITION
type Mutation {
deleteUser(id: ID!): Boolean @hasRole(role: "ADMIN")
}
Implementation:
-
PubSub setup with Redis:
const { RedisPubSub } = require('graphql-redis-subscriptions');
const pubsub = new RedisPubSub({
connection: {
host: process.env.REDIS_HOST,
port: 6379,
retryStrategy: times => Math.min(times * 50, 2000)
}
});
-
Subscription resolver:
const resolvers = {
Subscription: {
postCreated: {
subscribe: () => pubsub.asyncIterator(['POST_CREATED'])
}
},
Mutation: {
createPost: async (_, { input }) => {
const post = await Post.create(input);
await pubsub.publish('POST_CREATED', { postCreated: post });
return post;
}
}
};
Example:
Filtered subscriptions:
commentAdded: {
subscribe: withFilter(
() => pubsub.asyncIterator(['COMMENT_ADDED']),
(payload, variables) => {
return payload.commentAdded.postId === variables.postId;
}
)
}
Implementation:
-
Apollo Studio tracing:
const { ApolloServerPluginUsageReporting } = require('apollo-server-core');
const server = new ApolloServer({
plugins: [
ApolloServerPluginUsageReporting({
sendVariableValues: { all: true },
sendHeaders: { exceptNames: ['authorization'] }
})
]
});
-
Custom error formatting:
const formatError = (err) => {
const { extensions, message, path } = err;
if (extensions?.code === 'INTERNAL_SERVER_ERROR') {
logger.error('Internal error', {
stacktrace: extensions.exception.stacktrace,
query: extensions.query,
variables: extensions.variables
});
}
return {
message,
path,
code: extensions?.code,
timestamp: new Date().toISOString()
};
};
Example:
Distributed tracing with OpenTelemetry:
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const provider = new NodeTracerProvider();
provider.register();
const tracer = trace.getTracer('graphql-api');
const resolverWrapper = (resolver) => async (...args) => {
const span = tracer.startSpan(resolver.name);
try {
const result = await resolver(...args);
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (err) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: err.message
});
throw err;
} finally {
span.end();
}
};