Apollo Server with Node.js


  Once we understand the fundamentals of GraphQL, the next step is to learn how to build a server that can handle our queries and mutations.


  In the Node.js ecosystem, Apollo Server is the most popular and comprehensive library for this task.


  It offers a solid foundation and a wealth of features for building robust, scalable, and easy-to-maintain GraphQL APIs.


What is Apollo Server?

Apollo Server is an open-source GraphQL server implementation that is compatible with any data source (SQL databases, NoSQL, existing REST APIs, etc.). It integrates seamlessly with popular HTTP frameworks like Express.js, Koa, and Hapi, or can run standalone.


Its key features include:

  • Easy setup: Allows you to spin up a GraphQL server with just a few lines of code.
  • Integration with Express.js: Can be used as middleware in an existing Express application.
  • GraphQL schema support: Facilitates the definition and validation of your GraphQL schema.
  • Development console (Apollo Sandbox): Provides an interactive interface to test queries and mutations directly in the browser.
  • Data Sources: A pattern for encapsulating data access logic, making resolvers cleaner and easier to test.
  • Context: Allows shared information (like user authentication or Data Source instances) to be passed to all resolvers.

Components of an Apollo Server


  • Schema: Defined using GraphQL's Schema Definition Language (SDL). It describes the data types, queries, mutations, and subscriptions that your API exposes. It is represented as a GraphQL SDL string.
  • Resolvers: These are functions that tell GraphQL how to fetch the data for each field in your schema. When a client makes a query, Apollo Server invokes the corresponding resolvers to build the response.
    A resolver for a given field has the signature `(parent, args, context, info)`.
    • `parent`: The result of the parent field's resolver.
    • `args`: The arguments passed to this field in the GraphQL query.
    • `context`: An object shared among all resolvers of a GraphQL operation, useful for authentication or database access.
    • `info`: An object containing information about the query's execution state.
  • Data Sources: These are classes that encapsulate the logic for interacting with your databases, REST APIs, or other data sources. They help keep resolvers clean and reusable, and offer features like caching and request deduplication.
  • Context: An object that is built for each GraphQL request and passed to all resolvers. It's ideal for carrying context-sensitive information, such as the authenticated user, or for injecting services that resolvers need, like Data Source instances.

Building a Basic Apollo Server with Node.js

Let's create a simple example of a GraphQL API that manages a list of "Authors" and "Books" using Apollo Server in Node.js.


Step 1: Set up the project

mkdir apollo-nodejs-example
cd apollo-nodejs-example
npm init -y
npm install express @apollo/server graphql

Step 2: Create index.js

This file will contain our GraphQL schema, resolvers, and Apollo server configuration.

const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const express = require('express');
const http = require('http');
const cors = require('cors');

// Sample data (simulating a database)
let authors = [
  { id: '1', name: 'Stephen King', nationality: 'American' },
  { id: '2', name: 'Gabriel García Márquez', nationality: 'Colombian' },
];

let books = [
  { id: '101', title: 'It', authorId: '1' },
  { id: '102', title: 'One Hundred Years of Solitude', authorId: '2' },
  { id: '103', title: 'The Shining', authorId: '1' },
];

// 1. Define the schema (Type Definitions)
const typeDefs = `
  type Author {
    id: ID!
    name: String!
    nationality: String
    books: [Book!]! # An author has many books
  }

  type Book {
    id: ID!
    title: String!
    author: Author! # A book has one author
  }

  type Query {
    authors: [Author!]!
    author(id: ID!): Author
    books: [Book!]!
    book(id: ID!): Book
  }

  type Mutation {
    addAuthor(name: String!, nationality: String): Author!
    addBook(title: String!, authorId: ID!): Book!
  }
`;

// 2. Define the Resolvers
const resolvers = {
  Query: {
    authors: () => authors,
    author: (parent, args) => authors.find(a => a.id === args.id),
    books: () => books,
    book: (parent, args) => books.find(b => b.id === args.id),
  },
  // Resolver for the Author -> Books relationship
  Author: {
    books: (parent) => books.filter(book => book.authorId === parent.id),
  },
  // Resolver for the Book -> Author relationship
  Book: {
    author: (parent) => authors.find(author => author.id === parent.authorId),
  },
  Mutation: {
    addAuthor: (parent, args) => {
      const newAuthor = {
        id: String(authors.length + 1), // Simple ID generation
        name: args.name,
        nationality: args.nationality,
      };
      authors.push(newAuthor);
      return newAuthor;
    },
    addBook: (parent, args) => {
      const newBook = {
        id: String(books.length + 101), // Simple ID generation
        title: args.title,
        authorId: args.authorId,
      };
      // Check if the author exists
      const authorExists = authors.some(a => a.id === args.authorId);
      if (!authorExists) {
        throw new Error('Author not found for the given authorId');
      }
      books.push(newBook);
      return newBook;
    },
  },
};

// Configure and start the Apollo server
async function startApolloServer() {
  const app = express();
  const httpServer = http.createServer(app);

  const server = new ApolloServer({
    typeDefs,
    resolvers,
    // You can add plugins for extra functionalities, e.g., logging
    // plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  });

  await server.start();

  app.use(
    '/graphql',
    cors(),
    express.json(),
    expressMiddleware(server, {
      context: async ({ req }) => ({ token: req.headers.token }), // Example of how to pass a context
    }),
  );

  await new Promise((resolve) => httpServer.listen({ port: 4000 }, resolve));
  console.log(`🚀 GraphQL Server ready at http://localhost:4000/graphql`);
}

startApolloServer();

Step 3: Run the server

node index.js

Open your browser and go to http://localhost:4000/graphql. You should see the Apollo Sandbox interface, where you can test the following queries:

Examples of queries in Apollo Sandbox:

# Get all authors and their books
query {
  authors {
    id
    name
    nationality
    books {
      title
    }
  }
}

# Get a specific book and its author
query {
  book(id: "101") {
    title
    author {
      name
      nationality
    }
  }
}

# Add a new author
mutation {
  addAuthor(name: "Jane Austen", nationality: "British") {
    id
    name
  }
}

# Add a new book (make sure to use an existing authorId, e.g., 1 for Stephen King)
mutation {
  addBook(title: "Carrie", authorId: "1") {
    id
    title
    author {
      name
    }
  }
}

  This example gives you a solid foundation for understanding how Apollo Server connects your GraphQL schema with actual data through resolvers.


  From here, you can expand by connecting to real databases, adding authentication, and exploring other advanced features that Apollo Server offers.

JavaScript Concepts and Reference