Introduction to Apollo Server
Apollo Server is one of the most popular GraphQL server implementations for Node.js. It provides a production-ready, spec-compliant GraphQL server that works with any GraphQL schema. Apollo Server is easy to set up, highly performant, and comes with built-in features like error handling, metrics, and Apollo Studio integration.
Key Benefits: Apollo Server integrates seamlessly with popular Node.js frameworks (Express, Fastify, Koa), provides excellent TypeScript support, and includes powerful developer tools.
Installation
First, create a new Node.js project and install the required dependencies:
# Create project directory
mkdir graphql-server
cd graphql-server
# Initialize Node.js project
npm init -y
# Install Apollo Server and GraphQL
npm install @apollo/server graphql
# Install nodemon for development (optional)
npm install --save-dev nodemon
Update your package.json with a start script:
{
"name": "graphql-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
"dependencies": {
"@apollo/server": "^4.10.0",
"graphql": "^16.8.1"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}
Note: Setting "type": "module" allows you to use ES6 imports instead of CommonJS require statements.
Creating Your First Apollo Server
Create an index.js file with a basic Apollo Server setup:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
// Define your GraphQL schema
const typeDefs = `#graphql
type Book {
title: String!
author: String!
year: Int!
}
type Query {
books: [Book!]!
book(title: String!): Book
}
`;
// Sample data
const books = [
{ title: 'The Awakening', author: 'Kate Chopin', year: 1899 },
{ title: 'City of Glass', author: 'Paul Auster', year: 1985 },
{ title: 'أولاد حارتنا', author: 'نجيب محفوظ', year: 1959 }
];
// Define resolvers
const resolvers = {
Query: {
books: () => books,
book: (parent, args) => {
return books.find(book => book.title === args.title);
}
}
};
// Create Apollo Server instance
const server = new ApolloServer({
typeDefs,
resolvers,
});
// Start the server
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
});
console.log(`🚀 Server ready at ${url}`);
Understanding the Schema Definition
The typeDefs string contains your GraphQL schema using the Schema Definition Language (SDL):
const typeDefs = `#graphql
# The #graphql comment enables syntax highlighting
# Define a custom type
type Book {
title: String! # Non-nullable string
author: String!
year: Int! # Non-nullable integer
}
# Define available queries
type Query {
books: [Book!]! # Returns non-null array of non-null Books
book(title: String!): Book # Takes title argument, returns nullable Book
}
`;
Important: The #graphql comment at the start of the template literal enables GraphQL syntax highlighting in editors with GraphQL extensions installed.
Writing Resolvers
Resolvers are functions that handle the logic for fetching data for each field in your schema:
const resolvers = {
Query: {
// Resolver for books query
books: () => {
// Return all books
return books;
},
// Resolver for book query with arguments
book: (parent, args, context, info) => {
// args contains the arguments passed to the query
return books.find(book => book.title === args.title);
}
}
};
Resolver Function Parameters:
- parent: The result of the parent resolver (useful for nested resolvers)
- args: Arguments passed to the field in the query
- context: Shared object across all resolvers (useful for authentication, database connections)
- info: Information about the execution state of the query
Starting the Server
Run your server with one of these commands:
# Production mode
npm start
# Development mode with auto-reload
npm run dev
You should see the message:
🚀 Server ready at http://localhost:4000/
Using Apollo Sandbox
Apollo Server automatically provides Apollo Sandbox, a powerful GraphQL IDE. Open your browser and navigate to http://localhost:4000/.
Try These Queries: In Apollo Sandbox, test your server with these example queries.
# Query 1: Get all books
query GetAllBooks {
books {
title
author
year
}
}
# Query 2: Get a specific book
query GetBook {
book(title: "City of Glass") {
title
author
year
}
}
# Query 3: Use variables
query GetBookByTitle($title: String!) {
book(title: $title) {
title
author
year
}
}
# Variables for Query 3:
{
"title": "أولاد حارتنا"
}
Adding Mutations
Let's extend our schema to support creating new books:
const typeDefs = `#graphql
type Book {
id: ID!
title: String!
author: String!
year: Int!
}
input CreateBookInput {
title: String!
author: String!
year: Int!
}
type Query {
books: [Book!]!
book(id: ID!): Book
}
type Mutation {
createBook(input: CreateBookInput!): Book!
deleteBook(id: ID!): Boolean!
}
`;
// Update data structure with IDs
let books = [
{ id: '1', title: 'The Awakening', author: 'Kate Chopin', year: 1899 },
{ id: '2', title: 'City of Glass', author: 'Paul Auster', year: 1985 },
{ id: '3', title: 'أولاد حارتنا', author: 'نجيب محفوظ', year: 1959 }
];
let nextId = 4;
const resolvers = {
Query: {
books: () => books,
book: (parent, args) => books.find(book => book.id === args.id)
},
Mutation: {
createBook: (parent, args) => {
const newBook = {
id: String(nextId++),
...args.input
};
books.push(newBook);
return newBook;
},
deleteBook: (parent, args) => {
const index = books.findIndex(book => book.id === args.id);
if (index === -1) return false;
books.splice(index, 1);
return true;
}
}
};
Testing Mutations
Try these mutation examples in Apollo Sandbox:
# Create a new book
mutation CreateBook {
createBook(input: {
title: "1984"
author: "George Orwell"
year: 1949
}) {
id
title
author
year
}
}
# Delete a book
mutation DeleteBook {
deleteBook(id: "1")
}
Project Structure Best Practices
As your API grows, organize your code into separate files:
graphql-server/
├── src/
│ ├── schema/
│ │ ├── typeDefs.js # Schema definitions
│ │ └── resolvers.js # Resolver functions
│ ├── models/
│ │ └── book.js # Data models
│ ├── datasources/
│ │ └── bookAPI.js # Data fetching logic
│ └── utils/
│ └── helpers.js # Utility functions
├── index.js # Server entry point
└── package.json
Example: Separate typeDefs and resolvers
// src/schema/typeDefs.js
export const typeDefs = `#graphql
type Book {
id: ID!
title: String!
author: String!
year: Int!
}
# ... rest of schema
`;
// src/schema/resolvers.js
export const resolvers = {
Query: {
books: () => { /* ... */ },
book: (parent, args) => { /* ... */ }
},
Mutation: {
createBook: (parent, args) => { /* ... */ }
}
};
// index.js
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs } from './src/schema/typeDefs.js';
import { resolvers } from './src/schema/resolvers.js';
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 }
});
console.log(`🚀 Server ready at ${url}`);
Adding Context
The context is shared across all resolvers and is useful for authentication, database connections, and more:
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req }) => {
// Get authentication token from headers
const token = req.headers.authorization || '';
// Add user info to context
return {
user: getUserFromToken(token),
dataSources: {
booksAPI: new BooksAPI()
}
};
}
});
// Use context in resolvers
const resolvers = {
Query: {
books: (parent, args, context) => {
// Access context.user for authentication
if (!context.user) {
throw new Error('Not authenticated');
}
return context.dataSources.booksAPI.getAllBooks();
}
}
};
Security Note: Always validate and sanitize user input in your resolvers. Never trust client data without validation.
Error Handling
Apollo Server provides built-in error handling. You can throw errors in resolvers:
import { GraphQLError } from 'graphql';
const resolvers = {
Query: {
book: (parent, args) => {
const book = books.find(b => b.id === args.id);
if (!book) {
throw new GraphQLError('Book not found', {
extensions: {
code: 'NOT_FOUND',
argumentName: 'id'
}
});
}
return book;
}
}
};
Exercise: Build a simple GraphQL API for a task management system:
- Create a Task type with id, title, description, completed, and createdAt fields
- Implement queries: tasks, task(id), completedTasks
- Implement mutations: createTask, updateTask, deleteTask, toggleTask
- Use input types for create and update operations
- Add proper error handling for not found cases
- Test all operations in Apollo Sandbox
Summary
In this lesson, you've learned how to set up Apollo Server, define schemas and resolvers, handle queries and mutations, and structure your GraphQL project. Apollo Server provides an excellent foundation for building production-ready GraphQL APIs. In the next lessons, we'll explore advanced topics like connecting to databases, authentication, subscriptions, and performance optimization.