| name | apollo-server-patterns |
| user-invocable | false |
| description | Use when building GraphQL APIs with Apollo Server requiring resolvers, data sources, schema design, and federation. |
| allowed-tools | ["Read","Write","Edit","Grep","Glob","Bash"] |
Apollo Server Patterns
Master Apollo Server for building production-ready GraphQL APIs with proper
schema design, efficient resolvers, and scalable architecture.
Overview
Apollo Server is a spec-compliant GraphQL server that works with any GraphQL
schema. It provides features like schema stitching, federation, data sources,
and built-in monitoring for production GraphQL APIs.
Installation and Setup
Installing Apollo Server
npm install @apollo/server graphql express cors body-parser
npm install @apollo/server graphql
npm install graphql-tag dataloader
Basic Server Setup
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs } from './schema.js';
import { resolvers } from './resolvers.js';
const server = new ApolloServer({
typeDefs,
resolvers,
formatError: (formattedError, error) => {
if (formattedError.extensions?.code === 'INTERNAL_SERVER_ERROR') {
return {
...formattedError,
message: 'An internal error occurred'
};
}
return formattedError;
},
plugins: [
{
async requestDidStart() {
return {
async willSendResponse({ response }) {
console.log('Response sent');
}
};
}
}
]
});
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }) => {
const token = req.headers.authorization || '';
const user = await getUserFromToken(token);
return { user };
}
});
console.log(`Server ready at ${url}`);
Core Patterns
1. Schema Definition
import { gql } from 'graphql-tag';
export const typeDefs = gql`
type User {
id: ID!
email: String!
name: String!
posts: [Post!]!
createdAt: String!
}
type Post {
id: ID!
title: String!
body: String!
author: User!
comments: [Comment!]!
published: Boolean!
createdAt: String!
updatedAt: String!
Comment
ID
String
User
Post
String
CreatePostInput
String
String
UpdatePostInput
String
String
Boolean
User
user ID User
users Int, Int User
post ID Post
posts Boolean, ID Post
signup String, String, String AuthPayload
login String, String AuthPayload
createPost CreatePostInput Post
updatePost ID, UpdatePostInput Post
deletePost ID Boolean
createComment ID, String Comment
Post
commentAdded ID Comment
AuthPayload
String
User
`;
2. Resolvers
export const resolvers = {
Query: {
me: (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
return context.user;
},
user: async (parent, { id }, { dataSources }) => {
return dataSources.usersAPI.getUserById(id);
},
users: async (parent, { limit = 10, offset = 0 }, { dataSources }) => {
return dataSources.usersAPI.getUsers({ limit, offset });
},
post: async (parent, { id }, { dataSources }) => {
return dataSources.postsAPI.getPostById(id);
},
posts: async (parent, { published, authorId }, { dataSources }) => {
return dataSources.postsAPI.getPosts({ published, authorId });
}
},
Mutation: {
signup: async (parent, { email, password, name }, { dataSources }) => {
const user = await dataSources.usersAPI.createUser({
email,
password,
name
});
const token = (user);
{ token, user };
},
: (parent, { email, password }, { dataSources }) => {
user = dataSources..(email, password);
(!user) {
();
}
token = (user);
{ token, user };
},
: (parent, { input }, { user, dataSources }) => {
(!user) {
();
}
dataSources..({
...input,
: user.
});
},
: (parent, { id, input }, { user, dataSources }) => {
post = dataSources..(id);
(post. !== user.) {
();
}
dataSources..(id, input);
},
: (parent, { id }, { user, dataSources }) => {
post = dataSources..(id);
(post. !== user.) {
();
}
dataSources..(id);
;
}
},
: {
: (parent, args, { dataSources }) => {
dataSources..(parent.);
}
},
: {
: (parent, args, { dataSources }) => {
dataSources..(parent.);
},
: (parent, args, { dataSources }) => {
dataSources..(parent.);
}
},
: {
: (parent, args, { dataSources }) => {
dataSources..(parent.);
},
: (parent, args, { dataSources }) => {
dataSources..(parent.);
}
}
};
3. Data Sources
import { RESTDataSource } from '@apollo/datasource-rest';
export class UsersAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = 'https://api.example.com/';
}
async getUserById(id) {
return this.get(`users/${id}`);
}
async getUsers({ limit, offset }) {
return this.get('users', {
params: { limit, offset }
});
}
async createUser({ email, password, name }) {
return this.post('users', {
body: { email, password, name }
});
}
async authenticate(email, password) {
try {
const response = await this.post('auth/login', {
body: { email, password }
});
response.;
} (error) {
;
}
}
}
;
{
() {
. = db;
. = (..());
}
() {
posts = .
.()
.()
.(, ids);
ids.( posts.( post. === id));
}
() {
..(id);
}
() {
query = ..().();
(published !== ) {
query = query.(, published);
}
(authorId) {
query = query.(, authorId);
}
query;
}
() {
.
.()
.()
.(, authorId);
}
() {
[post] = .()
.({
title,
body,
: authorId,
: ,
: (),
: ()
})
.();
post;
}
() {
[post] = .()
.(, id)
.({
...updates,
: ()
})
.();
post;
}
() {
.().(, id).();
}
}
4. Context and Authentication
import jwt from 'jsonwebtoken';
import { UsersAPI } from './dataSources/UsersAPI.js';
import { PostsDB } from './dataSources/PostsDB.js';
import { CommentsDB } from './dataSources/CommentsDB.js';
export async function createContext({ req }) {
const token = req.headers.authorization?.replace('Bearer ', '') || '';
let user = null;
if (token) {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
user = await getUserById(decoded.userId);
} catch (error) {
console.error('Invalid token:', error);
}
}
const dataSources = {
usersAPI: new UsersAPI(),
: (db),
: (db)
};
{
user,
dataSources,
db
};
}
() {
(!user) {
();
}
}
() {
(user);
(user. !== role) {
();
}
}
5. Error Handling
import { GraphQLError } from 'graphql';
export class AuthenticationError extends GraphQLError {
constructor(message) {
super(message, {
extensions: {
code: 'UNAUTHENTICATED',
http: { status: 401 }
}
});
}
}
export class ForbiddenError extends GraphQLError {
constructor(message) {
super(message, {
extensions: {
code: 'FORBIDDEN',
http: { status: 403 }
}
});
}
}
export class ValidationError extends GraphQLError {
constructor(message, fields) {
super(message, {
extensions: {
code: 'BAD_USER_INPUT',
validationErrors: fields,
http: { status: 400 }
}
});
}
}
{ , } ;
resolvers = {
: {
: (parent, { id }, { user, dataSources }) => {
(!user) {
();
}
post = dataSources..(id);
(post. !== user.) {
();
}
dataSources..(id);
;
}
}
};
6. Subscriptions
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { createServer } from 'http';
import express from 'express';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
const typeDefs = gql`
type Subscription {
postCreated: Post!
commentAdded(postId: ID!): Comment!
}
`;
const resolvers = {
: {
: (parent, { input }, { user, dataSources }) => {
post = dataSources..({
...input,
: user.
});
pubsub.(, { : post });
post;
},
: (parent, { postId, body }, { user, dataSources }) => {
comment = dataSources..({
postId,
body,
: user.
});
pubsub.(, { : comment });
comment;
}
},
: {
: {
: pubsub.([])
},
: {
:
pubsub.([])
}
}
};
schema = ({ typeDefs, resolvers });
app = ();
httpServer = (app);
wsServer = ({
: httpServer,
:
});
serverCleanup = ({ schema }, wsServer);
server = ({
schema,
: [
({ httpServer }),
{
() {
{
() {
serverCleanup.();
}
};
}
}
]
});
server.();
app.(
,
(),
express.(),
(server, {
: createContext
})
);
httpServer.(, {
.();
});
7. Schema Directives
import { mapSchema, getDirective, MapperKind } from '@graphql-tools/utils';
import { defaultFieldResolver } from 'graphql';
const typeDefs = gql`
directive @auth(requires: Role = USER) on FIELD_DEFINITION | OBJECT
enum Role {
ADMIN
USER
GUEST
}
type Query {
me: User @auth
users: [User!]! @auth(requires: ADMIN)
}
`;
function authDirective(directiveName) {
return {
authDirectiveTypeDefs: `directive @${directiveName}(requires: Role = USER)
on FIELD_DEFINITION | OBJECT`,
authDirectiveTransformer: (schema) =>
(schema, {
[.]: {
authDirective = (
schema,
fieldConfig,
directiveName
)?.[];
(authDirective) {
{ requires } = authDirective;
{ resolve = defaultFieldResolver } = fieldConfig;
fieldConfig. = () {
{ user } = context;
(!user) {
();
}
(requires && user. !== requires) {
();
}
(source, args, context, info);
};
}
fieldConfig;
}
})
};
}
{ authDirectiveTypeDefs, authDirectiveTransformer } = ();
schema = ({
: [authDirectiveTypeDefs, typeDefs],
resolvers
});
schema = (schema);
8. Batching and Caching with DataLoader
import DataLoader from 'dataloader';
export function createLoaders(db) {
const userLoader = new DataLoader(async (userIds) => {
const users = await db
.select('*')
.from('users')
.whereIn('id', userIds);
return userIds.map(id => users.find(user => user.id === id));
});
const postLoader = new DataLoader(
async (postIds) => {
const posts = await db
.select('*')
.from('posts')
.whereIn('id', postIds);
return postIds.map(id => posts.find(post => post.id === id));
},
{
: (),
: key,
: ,
:
}
);
commentsByPostLoader = ( (postIds) => {
comments = db
.()
.()
.(, postIds);
postIds.(
comments.( comment. === postId)
);
});
{
userLoader,
postLoader,
commentsByPostLoader
};
}
() {
loaders = (db);
{
loaders,
};
}
resolvers = {
: {
: {
loaders..(parent.);
},
: {
loaders..(parent.);
}
}
};
9. Federation
import { ApolloServer } from '@apollo/server';
import { buildSubgraphSchema } from '@apollo/subgraph';
import gql from 'graphql-tag';
const typeDefs = gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0",
import: ["@key", "@shareable"])
type User @key(fields: "id") {
id: ID!
email: String!
name: String!
}
type Query {
user(id: ID!): User
users: [User
`;
resolvers = {
: {
: {
dataSources..(id);
},
: {
dataSources..();
}
},
: {
: {
dataSources..(user.);
}
}
};
server = ({
: ({ typeDefs, resolvers })
});
typeDefs = gql`;
resolvers = {
: {
: {
dataSources..(user.);
}
},
: {
: {
{ : , : post. };
}
}
};
10. Performance Monitoring
export const monitoringPlugin = {
async requestDidStart() {
const start = Date.now();
return {
async willSendResponse({ response, errors }) {
const duration = Date.now() - start;
console.log({
duration,
hasErrors: !!errors,
operationName: request.operationName
});
if (duration > 1000) {
await metrics.recordSlowQuery({
operation: request.operationName,
duration
});
}
},
async didEncounterErrors({ errors }) {
errors.forEach(error => {
console.error('GraphQL Error:', error);
errorTracker.captureException(error);
});
}
};
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [monitoringPlugin]
});
Best Practices
- Use DataLoader - Batch and cache database queries
- Implement proper auth - Secure resolvers with authentication
- Design schema carefully - Think about client needs first
- Use input types - Validate mutation inputs properly
- Handle errors gracefully - Return meaningful error messages
- Implement monitoring - Track performance and errors
- Use data sources - Separate data fetching logic
- Leverage federation - Split large schemas into subgraphs
- Cache appropriately - Use Redis for shared cache
- Document schema - Add descriptions to types and fields
Common Pitfalls
- N+1 query problems - Not using DataLoader for batching
- Over-fetching in resolvers - Loading unnecessary data
- Missing error handling - Not catching and formatting errors
- Poor schema design - Not following GraphQL best practices
- No authentication - Exposing sensitive data without auth
- Blocking operations - Synchronous operations in resolvers
- Memory leaks - Not cleaning up subscriptions
- Missing validation - Not validating input data
- Exposing internals - Leaking database errors to clients
- No rate limiting - Allowing unlimited query complexity
When to Use
- Building GraphQL APIs
- Creating microservices with federation
- Developing real-time applications
- Building mobile backends
- Creating unified API gateways
- Developing admin dashboards
- Building e-commerce platforms
- Creating content management systems
- Developing social platforms
- Building analytics APIs
Resources