Node.js GraphQL

GraphQL is a query language for APIs. Clients ask for exactly the fields they need, and the server returns exactly that data. One address serves every request. Many modern apps use GraphQL for flexible data loading.

The Restaurant Menu Idea

A REST API works like a set meal. You order the lunch box and receive every item inside, even food you skip. GraphQL works like an a la carte menu. You pick only the dishes you want.

REST:     GET /users/5  --> { id, name, email, phone, address, ... }
                            (all fields, needed or not)

GraphQL:  { user(id: 5) { name } }
                       --> { "user": { "name": "Asha" } }
                            (only the name)

REST and GraphQL Compared

+----------------+----------------------+---------------------+
| Topic          | REST                 | GraphQL             |
+----------------+----------------------+---------------------+
| Addresses      | Many                 | One (/graphql)      |
| Response shape | Fixed by server      | Chosen by client    |
| Related data   | Several requests     | One request         |
+----------------+----------------------+---------------------+

Core Ideas

  • Schema describes the data types and available operations.
  • Query reads data.
  • Mutation changes data.
  • Resolver is a function that fetches the data for a field.

Set Up a Server

Apollo Server is a popular GraphQL library.

npm install @apollo/server graphql

Write the Schema

The schema acts as a contract between client and server. Each field has a type. An exclamation mark means the value is required.

const typeDefs = `#graphql
  type Book {
    id: ID!
    title: String!
    author: String!
    year: Int
  }

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

  type Mutation {
    addBook(title: String!, author: String!, year: Int): Book!
  }
`;

Write the Resolvers

let books = [
  { id: "1", title: "Node Basics", author: "Asha", year: 2022 },
  { id: "2", title: "Async Magic", author: "Ravi", year: 2023 }
];

const resolvers = {
  Query: {
    books: () => books,
    book: (parent, args) => books.find((b) => b.id === args.id)
  },
  Mutation: {
    addBook: (parent, args) => {
      const book = { id: String(books.length + 1), ...args };
      books.push(book);
      return book;
    }
  }
};

Start the Server

const { ApolloServer } = require("@apollo/server");
const { startStandaloneServer } = require("@apollo/server/standalone");

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

startStandaloneServer(server, { listen: { port: 4000 } })
  .then(({ url }) => console.log("Server ready at " + url));

Open the address in a browser to use the built-in explorer.

How a Request Flows

Client query
{ book(id: "1") { title author } }
        |
        v
+---------------+   matches   +------------------+
|   Schema      | ----------> | Query.book       |
+---------------+             | resolver         |
                              +------------------+
                                      |
                                      v
                          Data source (array, database)
                                      |
                                      v
Response: { "data": { "book": { "title": "Node Basics", "author": "Asha" } } }

Send Queries

Ask for a List

{
  books {
    title
    year
  }
}

Ask with a Variable

query GetBook($id: ID!) {
  book(id: $id) {
    title
    author
  }
}

Change Data with a Mutation

mutation {
  addBook(title: "GraphQL Guide", author: "Meera", year: 2024) {
    id
    title
  }
}

Connect Related Data

GraphQL follows links between types in one request. Suppose each book belongs to an author with a list of books.

{
  author(id: "1") {
    name
    books {
      title
    }
  }
}

A REST client needs two calls for this task. GraphQL needs one.

Handle Errors

GraphQL returns status 200 even for many errors. The response carries an errors list. Throw a clear error inside a resolver for bad input.

book: (parent, args) => {
  const found = books.find((b) => b.id === args.id);
  if (!found) {
    throw new Error("Book not found");
  }
  return found;
}

When to Choose GraphQL

  • Mobile apps that need small responses.
  • Screens that combine data from many sources.
  • Teams with many different clients.

Simple APIs with few resources often work well with REST.

Practice Tasks

  • Add a deleteBook mutation.
  • Query only the titles of all books.
  • Add a genre field to the schema and return it.