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