Agent SkillsAgent Skills
mgd34msu

apollo-server

@mgd34msu/apollo-server
mgd34msu
6
1 forks
Updated 3/31/2026
View on GitHub

Builds GraphQL APIs with Apollo Server 4, schema design, resolvers, and data sources. Use when implementing GraphQL servers, building federated graphs, or integrating GraphQL with Node.js frameworks.

Installation

$npx agent-skills-cli install @mgd34msu/apollo-server
Claude Code
Cursor
Copilot
Codex
Antigravity

Details

Pathplugins/goodvibes/skills/webdev/api-layer/apollo-server/SKILL.md
Branchmain
Scoped Name@mgd34msu/apollo-server

Usage

After installing, this skill will be available to your AI coding assistant.

Verify installation:

npx agent-skills-cli list

Skill Instructions


name: apollo-server description: Builds GraphQL APIs with Apollo Server 4, schema design, resolvers, and data sources. Use when implementing GraphQL servers, building federated graphs, or integrating GraphQL with Node.js frameworks.

Apollo Server

Apollo Server is a spec-compliant GraphQL server for Node.js. Version 4+ is framework-agnostic and supports Express, Fastify, Lambda, and more.

Quick Start

npm install @apollo/server graphql

Standalone Server

import { ApolloServer } from '@apollo/server'
import { startStandaloneServer } from '@apollo/server/standalone'

// Type definitions
const typeDefs = `#graphql
  type User {
    id: ID!
    email: String!
    name: String
    posts: [Post!]!
  }

  type Post {
    id: ID!
    title: String!
    content: String
    author: User!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
    posts: [Post!]!
  }

  type Mutation {
    createUser(email: String!, name: String): User!
    createPost(title: String!, content: String, authorId: ID!): Post!
  }
`

// Resolvers
const resolvers = {
  Query: {
    users: () => db.users.findMany(),
    user: (_, { id }) => db.users.findById(id),
    posts: () => db.posts.findMany()
  },
  Mutation: {
    createUser: (_, { email, name }) => db.users.create({ email, name }),
    createPost: (_, { title, content, authorId }) =>
      db.posts.create({ title, content, authorId })
  },
  User: {
    posts: (user) => db.posts.findByAuthor(user.id)
  },
  Post: {
    author: (post) => db.users.findById(post.authorId)
  }
}

// Create server
const server = new ApolloServer({
  typeDefs,
  resolvers
})

// Start server
const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 }
})

console.log(`Server ready at ${url}`)

Express Integration

npm install @apollo/server express cors
import { ApolloServer } from '@apollo/server'
import { expressMiddleware } from '@apollo/server/express4'
import express from 'express'
import cors from 'cors'

const app = express()

const server = new ApolloServer({
  typeDefs,
  resolvers
})

await server.start()

app.use(
  '/graphql',
  cors(),
  express.json(),
  expressMiddleware(server, {
    context: async ({ req }) => ({
      token: req.headers.authorization,
      user: await getUserFromToken(req.headers.authorization)
    })
  })
)

app.listen(4000)

Context

Context Setup

interface Context {
  user: User | null
  dataSources: {
    users: UserDataSource
    posts: PostDataSource
  }
}

const server = new ApolloServer<Context>({
  typeDefs,
  resolvers
})

await server.start()

app.use(
  '/graphql',
  expressMiddleware(server, {
    context: async ({ req }): Promise<Context> => {
      const token = req.headers.authorization?.replace('Bearer ', '')
      const user = token ? await verifyToken(token) : null

      return {
        user,
        dataSources: {
          users: new UserDataSource(),
          posts: new PostDataSource()
        }
      }
    }
  })
)

Using Context in Resolvers

const resolvers = {
  Query: {
    me: (_, __, context) => context.user,
    users: (_, __, { dataSources }) => dataSources.users.findAll()
  },
  Mutation: {
    createPost: (_, args, { user, dataSources }) => {
      if (!user) throw new GraphQLError('Not authenticated')
      return dataSources.posts.create({ ...args, authorId: user.id })
    }
  }
}

Schema Design

Input Types

input CreateUserInput {
  email: String!
  name: String
  role: Role = USER
}

input UpdateUserInput {
  email: String
  name: String
  role: Role
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User!
}

Enums

enum Role {
  USER
  ADMIN
  MODERATOR
}

enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

Interfaces

interface Node {
  id: ID!
}

interface Timestamped {
  createdAt: DateTime!
  updatedAt: DateTime!
}

type User implements Node & Timestamped {
  id: ID!
  email: String!
  createdAt: DateTime!
  updatedAt: DateTime!
}

Unions

union SearchResult = User | Post | Comment

type Query {
  search(query: String!): [SearchResult!]!
}
const resolvers = {
  SearchResult: {
    __resolveType(obj) {
      if (obj.email) return 'User'
      if (obj.title) return 'Post'
      if (obj.body) return 'Comment'
      return null
    }
  }
}

Custom Scalars

npm install graphql-scalars
import { DateTimeResolver, EmailAddressResolver } from 'graphql-scalars'

const typeDefs = `#graphql
  scalar DateTime
  scalar EmailAddress

  type User {
    email: EmailAddress!
    createdAt: DateTime!
  }
`

const resolvers = {
  DateTime: DateTimeResolver,
  EmailAddress: EmailAddressResolver
}

Error Handling

GraphQL Errors

import { GraphQLError } from 'graphql'

const resolvers = {
  Query: {
    user: async (_, { id }, { dataSources }) => {
      const user = await dataSources.users.findById(id)

      if (!user) {
        throw new GraphQLError('User not found', {
          extensions: {
            code: 'NOT_FOUND',
            argumentName: 'id'
          }
        })
      }

      return user
    }
  }
}

Authentication Error

function requireAuth(context: Context) {
  if (!context.user) {
    throw new GraphQLError('You must be logged in', {
      extensions: { code: 'UNAUTHENTICATED' }
    })
  }
  return context.user
}

const resolvers = {
  Mutation: {
    createPost: (_, args, context) => {
      const user = requireAuth(context)
      return createPost({ ...args, authorId: user.id })
    }
  }
}

Error Formatting

const server = new ApolloServer({
  typeDefs,
  resolvers,
  formatError: (formattedError, error) => {
    // Log error
    console.error(error)

    // Hide internal errors in production
    if (process.env.NODE_ENV === 'production') {
      if (formattedError.extensions?.code === 'INTERNAL_SERVER_ERROR') {
        return {
          message: 'Internal server error',
          extensions: { code: 'INTERNAL_SERVER_ERROR' }
        }
      }
    }

    return formattedError
  }
})

Data Sources

REST Data Source

npm install @apollo/datasource-rest
import { RESTDataSource } from '@apollo/datasource-rest'

class UsersAPI extends RESTDataSource {
  override baseURL = 'https://api.example.com/'

  async getUser(id: string) {
    return this.get(`users/${id}`)
  }

  async getUsers() {
    return this.get('users')
  }

  async createUser(user: CreateUserInput) {
    return this.post('users', { body: user })
  }

  // Caching
  override willSendRequest(path, request) {
    request.headers['Authorization'] = this.context.token
  }
}

Database Data Source

import { PrismaClient } from '@prisma/client'

class UserDataSource {
  private prisma: PrismaClient

  constructor() {
    this.prisma = new PrismaClient()
  }

  async findById(id: string) {
    return this.prisma.user.findUnique({ where: { id } })
  }

  async findAll() {
    return this.prisma.user.findMany()
  }

  async create(data: CreateUserInput) {
    return this.prisma.user.create({ data })
  }
}

Pagination

Cursor-Based (Relay Style)

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

type UserEdge {
  node: User!
  cursor: String!
}

type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type Query {
  users(first: Int, after: String): UserConnection!
}
const resolvers = {
  Query: {
    users: async (_, { first = 10, after }, { dataSources }) => {
      const decodedCursor = after ? Buffer.from(after, 'base64').toString() : null
      const users = await dataSources.users.findMany({
        take: first + 1,
        cursor: decodedCursor ? { id: decodedCursor } : undefined,
        skip: decodedCursor ? 1 : 0
      })

      const hasNextPage = users.length > first
      const edges = users.slice(0, first).map(user => ({
        node: user,
        cursor: Buffer.from(user.id).toString('base64')
      }))

      return {
        edges,
        pageInfo: {
          hasNextPage,
          hasPreviousPage: !!after,
          startCursor: edges[0]?.cursor,
          endCursor: edges[edges.length - 1]?.cursor
        },
        totalCount: await dataSources.users.count()
      }
    }
  }
}

Subscriptions

npm install graphql-ws ws
import { createServer } from 'http'
import { WebSocketServer } from 'ws'
import { useServer } from 'graphql-ws/lib/use/ws'
import { makeExecutableSchema } from '@graphql-tools/schema'

const typeDefs = `#graphql
  type Subscription {
    messageCreated: Message!
    userTyping(channelId: ID!): User!
  }
`

const resolvers = {
  Subscription: {
    messageCreated: {
      subscribe: () => pubsub.asyncIterator(['MESSAGE_CREATED'])
    },
    userTyping: {
      subscribe: (_, { channelId }) =>
        pubsub.asyncIterator([`USER_TYPING_${channelId}`])
    }
  }
}

const schema = makeExecutableSchema({ typeDefs, resolvers })

const httpServer = createServer(app)

const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql'
})

useServer({ schema }, wsServer)

httpServer.listen(4000)

PubSub

import { PubSub } from 'graphql-subscriptions'

const pubsub = new PubSub()

const resolvers = {
  Mutation: {
    createMessage: async (_, { input }, { user }) => {
      const message = await db.messages.create({
        ...input,
        authorId: user.id
      })

      pubsub.publish('MESSAGE_CREATED', { messageCreated: message })

      return message
    }
  }
}

Plugins

Logging Plugin

const loggingPlugin = {
  async requestDidStart(requestContext) {
    console.log('Request started:', requestContext.request.query)

    return {
      async willSendResponse(requestContext) {
        console.log('Response:', requestContext.response)
      },
      async didEncounterErrors(requestContext) {
        console.error('Errors:', requestContext.errors)
      }
    }
  }
}

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [loggingPlugin]
})

Performance Plugin

import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting'

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [
    ApolloServerPluginUsageReporting({
      sendVariableValues: { all: true },
      sendHeaders: { all: true }
    })
  ]
})

Type Safety with Codegen

npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers
# codegen.yml
generates:
  ./src/generated/graphql.ts:
    plugins:
      - typescript
      - typescript-resolvers
    config:
      contextType: ../context#Context
      mappers:
        User: ../models#UserModel
import type { Resolvers } from './generated/graphql'

const resolvers: Resolvers = {
  Query: {
    // Fully typed resolvers
    user: (_, { id }) => db.users.findById(id)
  }
}

Best Practices

  1. Use input types for mutations
  2. Implement DataLoader for N+1 prevention
  3. Add query depth limiting to prevent abuse
  4. Use persisted queries in production
  5. Implement proper error codes
  6. Generate types with GraphQL Codegen

References