Back to Tutorials

Building GraphQL APIs with Apollo Server: Advanced Guide

Apollo Server Setup

Apollo Server is the most popular GraphQL server implementation, providing excellent tooling and performance.

Basic Apollo Server

const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');

const typeDefs = gql`
    type User {
        id: ID!
        name: String!
        email: 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(name: String!, email: String!): User!
        createPost(title: String!, content: String!, authorId: ID!): Post!
    }
`;

const resolvers = {
    Query: {
        users: () => users,
        user: (parent, args) => users.find(u => u.id === args.id),
        posts: () => posts
    },
    Mutation: {
        createUser: (parent, args) => {
            const user = { id: String(users.length + 1), ...args, posts: [] };
            users.push(user);
            return user;
        },
        createPost: (parent, args) => {
            const post = { id: String(posts.length + 1), ...args };
            posts.push(post);
            return post;
        }
    },
    User: {
        posts: (parent) => posts.filter(p => p.authorId === parent.id)
    }
};

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

await server.start();
server.applyMiddleware({ app });

app.listen(4000, () => {
    console.log(`Server ready at http://localhost:4000${server.graphqlPath}`);
});

Data Sources

const { RESTDataSource } = require('apollo-datasource-rest');

class UserAPI extends RESTDataSource {
    constructor() {
        super();
        this.baseURL = 'https://api.example.com/';
    }
    
    async getUser(id) {
        return this.get(`users/${id}`);
    }
    
    async getUsers() {
        return this.get('users');
    }
}

const server = new ApolloServer({
    typeDefs,
    resolvers,
    dataSources: () => ({
        userAPI: new UserAPI()
    })
});

// In resolver
const resolvers = {
    Query: {
        user: (parent, args, { dataSources }) => {
            return dataSources.userAPI.getUser(args.id);
        }
    }
};

Authentication

const server = new ApolloServer({
    typeDefs,
    resolvers,
    context: ({ req }) => {
        const token = req.headers.authorization || '';
        const user = getUserFromToken(token);
        return { user };
    }
});

// In resolver
const resolvers = {
    Query: {
        protectedData: (parent, args, { user }) => {
            if (!user) {
                throw new AuthenticationError('Must authenticate');
            }
            return getProtectedData();
        }
    }
};

Subscriptions

const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();

const typeDefs = gql`
    type Subscription {
        postAdded: Post!
    }
`;

const resolvers = {
    Subscription: {
        postAdded: {
            subscribe: () => pubsub.asyncIterator(['POST_ADDED'])
        }
    },
    Mutation: {
        createPost: (parent, args) => {
            const post = createPost(args);
            pubsub.publish('POST_ADDED', { postAdded: post });
            return post;
        }
    }
};

Best Practices

  • Use DataLoader for N+1 query problems
  • Implement proper error handling
  • Use field-level permissions
  • Validate input with custom scalars
  • Use fragments for reusable queries