GraphQL APIs

Reviewed & published by Brayan K

GraphQL is a query language for APIs where the client asks for exactly the fields it needs from a single, strongly typed endpoint, and the server answers using one resolver function per field.

Part of the free Node.js course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll compare GraphQL with REST, write a schema with types, queries, and mutations, understand how resolvers fetch each field, see the core query executor in action, and wire it all together with Apollo Server.

What You'll Learn in This Lesson

1️⃣ GraphQL vs REST: the Schema

A GraphQL API starts with a schema written in the Schema Definition Language (SDL). It declares your types (like User), the readable fields under type Query, and the write operations under type Mutation. A ! marks a field as non-null (required).

Where REST might force you to call /users/1 and get the whole record, a GraphQL query names just the fields it wants — so the client decides the response shape, avoiding both over-fetching and under-fetching.

# A GraphQL SCHEMA describes your data's types and what can be queried.
# This is the GraphQL Schema Definition Language (SDL), not JavaScript.

type User {
  id: ID!          # ! means non-null (required)
  name: String!
  email: String!
  role: String!
}

type Query {
  user(id: ID!): User      # fetch one user by id
  users: [User!]!          # fetch the whole list
}

type Mutation {
  addUser(name: String!, email: String!): User
}

# A client QUERY asks for exactly the fields it wants — no more, no less:
#
#   query {
#     user(id: 1) { name role }   # note: email is NOT requested
#   }

2️⃣ Resolvers: How Fields Get Their Values

The schema says what exists; resolvers say how to fetch it. A resolver is one function per field that returns its value — from a database, another API, or an array. GraphQL then keeps only the fields the client asked for. The runnable executor below models exactly that: it resolves the data, then picks just the requested fields.

3️⃣ The Real Thing: Apollo Server

In production you hand your typeDefs (the SDL) and your resolvers to Apollo Server, which parses each incoming query, validates it against the schema, calls the right resolvers, and assembles the response — plus a sandbox UI for exploring your API. Notice resolvers are grouped under Query and Mutation, and each receives (parent, args, context).

// The real thing with Apollo Server.
// Install once:  npm install @apollo/server graphql
const { ApolloServer } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone');

// 1) The schema (typeDefs) — same SDL as above.
const typeDefs = `#graphql
  type User { id: ID!, name: String!, email: String!, role: String! }
  type Query { user(id: ID!): User, users: [User!]! }
  type Mutation { addUser(name: String!, email: String!): User }
`;

const users = [{ id: '1', name: 'Ada', email: 'ada@x.com', role: 'admin' }];

// 2) Resolvers — one function per field, grouped by Query/Mutation.
const resolvers = {
  Query: {
    user: (_parent, { id }) => users.find((u) => u.id === id),
    users: () => users,
  },
  Mutation: {
    addUser: (_parent, { name, email }) => {
      const user = { id: String(users.length + 1), name, email, role: 'user' };
      users.push(user);
      return user;
    },
  },
};

// 3) Start the server — Apollo wires schema + resolvers together.
const server = new ApolloServer({ typeDefs, resolvers });
startStandaloneServer(server, { listen: { port: 4000 } })
  .then(({ url }) => console.log(`Ready at ${url}`));

Your turn. The executor below works once you fill in the field list marked ___. Follow the 👉 hint, then run it.

// 🎯 YOUR TURN — replace each ___ then run it.
const products = [
  { id: 1, title: 'Keyboard', price: 80, stock: 5 },
  { id: 2, title: 'Mouse', price: 25, stock: 0 },
];
const resolvers = {
  products: () => products,
};
function execute(rootField, fields) {
  const data = resolvers[rootField]();
  return data.map((obj) => Object.fromEntries(fields.map((f) => [f, obj[f]])));
}

// Ask for only title + price of every product (fill in the field list):
console.log(execute('products', [___, ___]));   // 👉 'title', 'price'

// ✅ Expected output:
//    [ { title: 'Keyboard', price: 80 }, { title: 'Mouse', price: 25 } ]

Mini-Challenge: A Query + a Mutation

No blanks — just a brief and an outline. Build a resolver map with a Query.todos reader and a Mutation.addTodo writer, then show the list growing. Run it and compare with the example output.

// 🎯 MINI-CHALLENGE: a resolver map with a query AND a mutation
// 1. Keep todos in db = { todos: [{ id: 1, text: 'Learn GraphQL', done: false }] }.
// 2. resolvers.Query.todos() returns db.todos.
// 3. resolvers.Mutation.addTodo({ text }) pushes a new { id, text, done:false }
//    and returns it.
// 4. Print the list before, the added todo, and the list after.
//
// ✅ Example output:
//    Before: [ { id: 1, text: 'Learn GraphQL', done: false } ]
//    Added:  { id: 2, text: 'Ship app', done: false }
//    After:  [ ...two todos... ]

const db = { todos: [{ id: 1, text: 'Learn GraphQL', done: false }] };
// your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — GraphQL Building Blocks

ConceptDetail
Define a typetype User { id: ID! }
Required fieldname: String!
Read datatype Query
Write datatype Mutation
Resolver signature(parent, args, context)
Run a servernew ApolloServer(...)
Fix N+1DataLoader

🎉 Lesson Complete!

Practice quiz

What is a key advantage of GraphQL over a typical REST endpoint?

  • It requires no server
  • The client asks for exactly the fields it wants
  • It only returns XML
  • It cannot be queried

Answer: The client asks for exactly the fields it wants. A GraphQL client requests precisely the fields it needs, no more, no less.

In GraphQL SDL, what does the ! after a type mean, as in String!?

  • The field is non-null (required)
  • The field is deprecated
  • The field is a list
  • The field is optional

Answer: The field is non-null (required). ! marks a field as non-null, meaning it must always have a value.

What is a resolver in GraphQL?

  • A database table
  • A caching layer
  • A function that returns the value for a field
  • A type definition

Answer: A function that returns the value for a field. Each field has a resolver function that produces its value.

Which special type holds read operations in a GraphQL schema?

  • Mutation
  • Subscription
  • Schema
  • Query

Answer: Query. The Query type defines the fields clients can read/fetch.

Which type is used for operations that CHANGE data, like addUser?

  • Mutation
  • Query
  • Fragment
  • Scalar

Answer: Mutation. The Mutation type defines write operations such as adding a user.

What does the type notation [User!]! describe?

  • A single optional user
  • A non-null list of non-null Users
  • A string of users
  • An empty list

Answer: A non-null list of non-null Users. [User!]! is a required list whose every element is a non-null User.

Which library is shown for building a real GraphQL server in the lesson?

  • Express GraphQL Classic
  • GraphiQL only
  • Apollo Server (@apollo/server)
  • Mongoose

Answer: Apollo Server (@apollo/server). The lesson uses @apollo/server with startStandaloneServer.

How does a client avoid over-fetching with GraphQL?

  • It downloads the whole database
  • The server always sends every field
  • It uses multiple endpoints
  • It lists only the fields it needs in the query

Answer: It lists only the fields it needs in the query. By naming just the fields it wants, the client gets exactly that data.

In Apollo Server, how are resolvers organized?

  • Grouped by type, e.g. Query and Mutation
  • In a single flat array
  • Inside the database
  • As URL routes

Answer: Grouped by type, e.g. Query and Mutation. Resolvers are grouped under Query, Mutation, etc., one function per field.

What is the ID type's role in the User schema?

  • It stores an image
  • It is a unique identifier for the object
  • It is the user's password
  • It is a timestamp

Answer: It is a unique identifier for the object. ID is a scalar serving as a unique identifier for an object.

Continue this course

Frequently asked questions

What is the main difference between GraphQL and REST?

With REST, the server decides the shape of each endpoint's response, so you often get more data than you need (over-fetching) or have to call several endpoints to assemble one screen (under-fetching). With GraphQL, there's typically a single endpoint and the client sends a query describing exactly which fields it wants — including nested, related data — and gets back precisely that, in one round trip. The trade-offs: REST is simpler, caches naturally over HTTP, and is easy to debug; GraphQL gives clients flexibility and a strongly typed schema but needs more server setup and careful handling of caching and expensive queries.

What is a resolver in GraphQL?

A resolver is the function that produces the value for a single field in your schema. When a query asks for a field, GraphQL calls that field's resolver, passing it the parent object, the arguments, and a shared context (often holding the database connection or the authenticated user). For example, a Query.user resolver looks up a user by id; a User.posts resolver fetches that user's posts. Resolvers are where your real data-fetching lives — calling a database, another API, or in our runnable example, picking values out of an in-memory array. The schema says what's available; resolvers say how to get it.

What is the difference between a query and a mutation?

By convention, a query READS data and should have no side effects, while a mutation WRITES data — creating, updating, or deleting it. They look almost identical and both run through resolvers, but separating them communicates intent and lets tooling treat them differently (for instance, mutations run one at a time in series to avoid race conditions, whereas a query's fields can resolve in parallel). In SDL you declare readable fields under type Query and write operations under type Mutation, then provide a resolver for each — exactly the Query/Mutation split shown in this lesson's runnable challenge.

What is Apollo Server and do I need it?

Apollo Server is the most popular Node library for running a GraphQL API. You give it your schema (typeDefs in SDL) and your resolvers, and it handles parsing incoming queries, validating them against the schema, calling the right resolvers, assembling the response, and reporting errors — plus a built-in sandbox UI for exploring your API. You don't strictly need it (the lower-level graphql package or alternatives like GraphQL Yoga also work), but Apollo's standalone server gets you from schema to a running endpoint in a few lines, which is why it's the common starting point. The tiny executor in this lesson shows the core idea Apollo automates for you.