Claude
Skills
Sign in
Back

apollo-server-patterns

Included with Lifetime
$97 forever

Use when building GraphQL APIs with Apollo Server requiring resolvers, data sources, schema design, and federation.

Design

What this skill does


# 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

```bash
# For Express
npm install @apollo/server graphql express cors body-parser

# For standalone server
npm install @apollo/server graphql

# Additional utilities
npm install graphql-tag dataloader
```

### Basic Server Setup

```javascript
// server.js
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) => {
    // Custom error formatting
    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

```javascript
// schema.js
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!
  }

  type Comment {
    id: ID!
    body: String!
    author: User!
    post: Post!
    createdAt: String!
  }

  input CreatePostInput {
    title: String!
    body: String!
  }

  input UpdatePostInput {
    title: String
    body: String
    published: Boolean
  }

  type Query {
    me: User
    user(id: ID!): User
    users(limit: Int, offset: Int): [User!]!
    post(id: ID!): Post
    posts(published: Boolean, authorId: ID): [Post!]!
  }

  type Mutation {
    signup(email: String!, password: String!, name: String!): AuthPayload!
    login(email: String!, password: String!): AuthPayload!
    createPost(input: CreatePostInput!): Post!
    updatePost(id: ID!, input: UpdatePostInput!): Post!
    deletePost(id: ID!): Boolean!
    createComment(postId: ID!, body: String!): Comment!
  }

  type Subscription {
    postCreated: Post!
    commentAdded(postId: ID!): Comment!
  }

  type AuthPayload {
    token: String!
    user: User!
  }
`;
```

### 2. Resolvers

```javascript
// resolvers.js
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 = generateToken(user);
      return { token, user };
    },

    login: async (parent, { email, password }, { dataSources }) => {
      const user = await dataSources.usersAPI.authenticate(email, password);
      if (!user) {
        throw new Error('Invalid credentials');
      }
      const token = generateToken(user);
      return { token, user };
    },

    createPost: async (parent, { input }, { user, dataSources }) => {
      if (!user) {
        throw new Error('Not authenticated');
      }
      return dataSources.postsAPI.createPost({
        ...input,
        authorId: user.id
      });
    },

    updatePost: async (parent, { id, input }, { user, dataSources }) => {
      const post = await dataSources.postsAPI.getPostById(id);
      if (post.authorId !== user.id) {
        throw new Error('Not authorized');
      }
      return dataSources.postsAPI.updatePost(id, input);
    },

    deletePost: async (parent, { id }, { user, dataSources }) => {
      const post = await dataSources.postsAPI.getPostById(id);
      if (post.authorId !== user.id) {
        throw new Error('Not authorized');
      }
      await dataSources.postsAPI.deletePost(id);
      return true;
    }
  },

  // Field resolvers
  User: {
    posts: async (parent, args, { dataSources }) => {
      return dataSources.postsAPI.getPostsByAuthorId(parent.id);
    }
  },

  Post: {
    author: async (parent, args, { dataSources }) => {
      return dataSources.usersAPI.getUserById(parent.authorId);
    },

    comments: async (parent, args, { dataSources }) => {
      return dataSources.commentsAPI.getCommentsByPostId(parent.id);
    }
  },

  Comment: {
    author: async (parent, args, { dataSources }) => {
      return dataSources.usersAPI.getUserById(parent.authorId);
    },

    post: async (parent, args, { dataSources }) => {
      return dataSources.postsAPI.getPostById(parent.postId);
    }
  }
};
```

### 3. Data Sources

```javascript
// dataSources/UsersAPI.js
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 }
      });
      return response.user;
    } catch (error) {
      return null;
    }
  }
}

// dataSources/PostsDB.js
import DataLoader from 'dataloader';

export class PostsDB {
  constructor(db) {
    this.db = db;
    this.loader = new DataLoader(this.batchGetPosts.bind(this));
  }

  async batchGetPosts(ids) {
    const posts = await this.db
      .select('*')
      .from('posts')
      .whereIn('id', ids);

    // Return posts in same order as ids
    return ids.map(id => posts.find(post => post.id === id));
  }

  async getPostById(id) {
    return this.loader.load(id);
  }

  async getPosts({ published, authorId }) {
    let query = this.db.select('*').from('posts');

    if (published !== undefined) {
      query = query.where('published', published);
    }

    if (authorId) {
      query = query.where('author_id', authorId);
    }

    return query;
  }

  async getPostsByAuthorId(authorId) {
    return this.db
      .select('*')
      .from('posts')
      .where('author_id', authorId);
  }

  async createPost({ title, body, authorId }) {
    const [post] = await this.db('posts')
      .insert({
        title,
        body,
        author_id: authorId,
        published: false,
        created_at: new Date(),
        updated_at: new Date()
      }

Related in Design