GraphQL

Setting Up Apollo Server

20 min Lesson 5 of 35

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.