Smart Technology Tips to Fix, Optimize and Understand Your Devices

Practical guides for computers, mobile devices and everyday tech problems.

GraphQL Introduction: Learn Queries, Mutations, and Your First API Step-by-Step

11 min read
A beginner-friendly GraphQL introduction that teaches schemas, queries, mutations, and how to build and test your first GraphQL API step-by-step.
GraphQL single endpoint connecting to multiple resources illustration

GraphQL typically uses one endpoint that can fetch many related resources in a single request.

Last updated: February 2026 ✅

GraphQL is a modern way to request data from an API — especially when your app needs exactly the right fields, no more and no less.

If REST sometimes feels like:

  • “Why am I receiving 20 fields when I only need 3?”
  • “Why do I need 3 different endpoints to build one screen?”
  • “Why is the frontend waiting on multiple requests?”

GraphQL is designed to solve those problems with one big idea:

The client asks for exactly what it needs.

This tutorial is beginner-first and practical.
You’ll learn GraphQL by building a small API and actually using it.


✅ Key Takeaways


🧭 Quick Navigation


🟣 What Is GraphQL?

GraphQL is a query language for APIs and a runtime for executing those queries.

Instead of calling multiple REST endpoints like:

  • /users/1
  • /users/1/posts
  • /posts/50/comments

GraphQL typically exposes one endpoint, often:

  • /graphql

Then your client sends a query that describes the data it wants.

The key difference (beginner-friendly)

In REST, the server decides the response shape.
In GraphQL, the client decides the response shape (within the schema rules).

That makes GraphQL feel very “frontend-friendly”, but it’s not only for frontend — it’s great for any client that needs flexible data.


🆚 GraphQL vs REST (Simple Comparison)

Here’s a strong beginner comparison table you can keep as a reference:

TopicRESTGraphQL
EndpointsMany endpointsUsually one endpoint (/graphql)
Response shapeFixed by serverChosen by client (query fields)
Over-fetchingCommonReduced (request only fields)
Under-fetchingCommon (multiple requests)Often reduced (one request can include nested data)
CachingStraightforward with URLsPossible, but different approach
Error styleOften HTTP status codesOften 200 with errors in JSON
Learning curveSimple at firstRequires understanding schema + queries

✅ Both are useful.
GraphQL is not “better”, it’s “better for certain situations”.


🧰 Tools You’ll Use (Programs + Why)

You can build GraphQL in many languages, but for beginners, a JavaScript setup is very practical.

1) Code editor

  • VS Code (easy, extensions, clean workflow)

2) Runtime

3) GraphQL testing UI

You’ll test queries in a GraphQL explorer (interactive UI). Many servers provide this automatically.

You may also use:

  • curl (quick tests)
  • Postman (possible, but GraphQL Explorer is usually easier)

🧩 Core Ideas: Schema, Types, Queries, Mutations

GraphQL feels easy when you understand the vocabulary.

✅ Schema

The schema is the API contract.

It defines:

  • What types exist
  • What you can query (read)
  • What you can mutate (change)

✅ Type

A type is a “shape” of data.

Example: a Task type could have:

  • id
  • title
  • done

✅ Query

A query reads data.

Example:

  • “Give me tasks with id and title.”

✅ Mutation

A mutation changes data.

Example:

  • “Create a new task.”
  • “Mark a task as done.”

✅ Resolver

A resolver is the function that returns the data for a field.

Think of it like:

  • Schema defines what can be asked
  • Resolver defines how to answer
GraphQL schema and types concept illustration
The schema defines types and allowed operations, acting as the API contract.

🧾 Schema and Types (Your First Mental Model)

This is what a simple schema looks like (we’ll build it soon):

type Task {
  id: ID!
  title: String!
  done: Boolean!
}

type Query {
  tasks: [Task!]!
  task(id: ID!): Task
}

type Mutation {
  createTask(title: String!): Task!
  setDone(id: ID!, done: Boolean!): Task!
}

What the symbols mean (beginner explanation)

  • Task is a type
  • Query lists allowed read operations
  • Mutation lists allowed write operations
  • ! means “required / cannot be null”
  • [Task!]! means “an array that always exists, and tasks inside it are not null”

🏗️ Build Your First GraphQL API (Node + Apollo)

We will build a small Tasks GraphQL API.

Features:

  • list tasks
  • get one task
  • create task
  • update task done state

We’ll start with an in-memory array (simple), then you can upgrade later.


✅ Step 1: Create a project folder

Create a folder:

  • graphql-introduction

Open it in VS Code.


✅ Step 2: Initialize Node project

In the terminal:

npm init -y

✅ Step 3: Install Apollo Server + GraphQL

npm install @apollo/server graphql

We’ll also install a Node HTTP helper:

npm install express cors

And a small adapter for Apollo + Express:

npm install @as-integrations/express5

✅ Step 4: Create server.js

Create server.js and paste everything below:

import express from "express";
import cors from "cors";
import { ApolloServer } from "@apollo/server";
import { expressMiddleware } from "@as-integrations/express5";

// 1) In-memory data (simple "database")
let tasks = [
  { id: "1", title: "Learn GraphQL basics", done: false },
  { id: "2", title: "Run my first query", done: false }
];

let nextId = 3;

// 2) GraphQL schema (API contract)
const typeDefs = `#graphql
  type Task {
    id: ID!
    title: String!
    done: Boolean!
  }

  type Query {
    tasks: [Task!]!
    task(id: ID!): Task
  }

  type Mutation {
    createTask(title: String!): Task!
    setDone(id: ID!, done: Boolean!): Task!
  }
`;

// 3) Resolvers (how we answer schema fields)
const resolvers = {
  Query: {
    tasks: () => tasks,
    task: (_, args) => tasks.find(t => t.id === args.id) || null
  },
  Mutation: {
    createTask: (_, args) => {
      const title = args.title.trim();
      if (!title) {
        throw new Error("Title cannot be empty");
      }

      const newTask = { id: String(nextId++), title, done: false };
      tasks.push(newTask);
      return newTask;
    },
    setDone: (_, args) => {
      const task = tasks.find(t => t.id === args.id);
      if (!task) {
        throw new Error("Task not found");
      }
      task.done = args.done;
      return task;
    }
  }
};

async function start() {
  const app = express();

  // Apollo Server instance
  const server = new ApolloServer({
    typeDefs,
    resolvers
  });

  await server.start();

  // Middleware
  app.use(cors());
  app.use(express.json());

  // GraphQL endpoint
  app.use("/graphql", expressMiddleware(server));

  const PORT = 4000;
  app.listen(PORT, () => {
    console.log(`✅ GraphQL running at http://localhost:${PORT}/graphql`);
  });
}

start();

✅ Step 5: Enable ES Modules (important)

Because we used import, Node needs module mode.

Open package.json and add:

"type": "module"

Example:

{
  "name": "graphql-introduction",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    "@apollo/server": "...",
    "@as-integrations/express5": "...",
    "cors": "...",
    "express": "...",
    "graphql": "..."
  }
}

✅ Step 6: Run the server

node server.js

You should see:

  • GraphQL running at http://localhost:4000/graphql

Open that URL in your browser.
You should see a GraphQL explorer UI.


🧪 Test Queries & Mutations (Explorer)

GraphQL query and response concept illustration
GraphQL responses match the exact field shape requested in the query.

Now you will run your first query.

✅ Query 1: List tasks

Paste this into the explorer:

query {
  tasks {
    id
    title
    done
  }
}

What you should see (explained)

You’ll get a JSON response like:

  • an object with data
  • inside it, tasks array
  • each task has the fields you requested

✅ Notice: you only receive the fields you listed.


✅ Query 2: Get one task by ID

query {
  task(id: "1") {
    id
    title
    done
  }
}

If the task doesn’t exist, it returns null.


✅ Mutation 1: Create a task

mutation {
  createTask(title: "Build a mutation") {
    id
    title
    done
  }
}

What happened (simple explanation)

  • You called a “write” operation
  • Apollo ran the resolver for createTask
  • The resolver created and returned the new task

✅ Mutation 2: Update done state

mutation {
  setDone(id: "1", done: true) {
    id
    title
    done
  }
}

Now query tasks again and confirm the change.


🧬 Why GraphQL Feels Different (Response Shape)

In REST, the server decides everything.

In GraphQL, these two queries can return different shapes even though they hit the same endpoint:

Request only titles

query {
  tasks {
    title
  }
}

Request everything

query {
  tasks {
    id
    title
    done
  }
}

That’s the core benefit: precision.


🖥️ Use GraphQL from a Frontend (fetch)

Now let’s call GraphQL from a basic HTML file.

Create index.html:

<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>GraphQL Fetch Demo</title>
</head>
<body>
  <h1>GraphQL Demo</h1>
  <button id="load">Load Tasks</button>
  <pre id="out"></pre>

  <script>
    const out = document.getElementById("out");

    async function gqlRequest(query, variables = {}) {
      const res = await fetch("http://localhost:4000/graphql", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ query, variables })
      });

      return res.json();
    }

    document.getElementById("load").addEventListener("click", async () => {
      const query = `
        query {
          tasks {
            id
            title
            done
          }
        }
      `;

      const data = await gqlRequest(query);
      out.textContent = JSON.stringify(data, null, 2);
    });
  </script>
</body>
</html>

What this code does (clear breakdown)

  • fetch() sends a POST request to /graphql
  • Body contains { query, variables }
  • Server returns { data } (and sometimes { errors })
  • We print the JSON result

If your browser blocks it, confirm:

  • server uses cors()
  • you’re calling the correct port 4000

🧱 Practical Patterns (Pagination, Errors, Validation)

GraphQL is powerful, but beginners should learn a few safe rules.

✅ Pattern 1: Use variables (cleaner queries)

Instead of hardcoding IDs in queries, use variables.

query GetTask($id: ID!) {
  task(id: $id) {
    id
    title
    done
  }
}

Variables:

{ "id": "1" }

Why this is good:

  • safer
  • reusable
  • easier to build frontend code

✅ Pattern 2: Understand GraphQL errors

GraphQL often returns HTTP 200 even when something fails.

Errors appear in an errors array.

Example: setDone(id:"999") may return:

  • data partially or null
  • errors describing the issue

That’s normal in GraphQL.


✅ Pattern 3: Add input types (cleaner mutations)

As APIs grow, you don’t want 10 arguments per mutation.

You can define an input type:

input CreateTaskInput {
  title: String!
}

Then mutation becomes:

createTask(input: CreateTaskInput!): Task!

This is cleaner and scales better.

GraphQL variables concept illustration
Variables keep queries reusable and safer by separating dynamic values from the query text.

📊 GraphQL Cheat Sheet Table (Beginner Quick Reference)

ConceptWhat it meansExample
TypeShape of datatype Task { id: ID! }
QueryRead operationquery { tasks { id } }
MutationWrite operationmutation { createTask(...) { id } }
ResolverFunction that returns datatasks: () => tasks
VariablesDynamic values$id: ID!
Non-null !Requiredtitle: String!
List []Array[Task!]!

Save this table. It helps a lot when you feel lost.


⚠️ Common GraphQL Mistakes

MistakeWhy it happensFix
Treating GraphQL like REST endpointsHabit from RESTThink “one endpoint, many queries”
Making schema too flexible earlyOverengineeringStart simple, expand slowly
Not validating inputsTrusting clientTrim + validate in resolvers
Returning confusing errorsThrowing random errorsUse clear error messages and consistent patterns
Requesting too many fields“Just in case” mindsetRequest only what the screen needs
Forgetting CORSFrontend blockedKeep cors() enabled for local dev

🧪 Exercises / Mini Projects

✅ Exercise 1: Add a deleteTask mutation

Goal: remove a task by ID.

Steps:

  1. Add schema mutation deleteTask(id: ID!): Boolean!
  2. Implement resolver to remove item
  3. Return true if deleted, false if not found
👉 Click here to see the solution
 // 1) Add in schema: // type Mutation { deleteTask(id: ID!): Boolean! ... } // 2) Add in resolvers: deleteTask: (_, args) => { const index = tasks.findIndex(t => t.id === args.id); if (index === -1) return false; tasks.splice(index, 1); return true; } 

✅ Exercise 2: Filter tasks by done

Goal: query only completed tasks.

Add:

tasks(done: Boolean): [Task!]!

Then resolver checks args.

👉 Click here to see the solution
 // Schema: // type Query { tasks(done: Boolean): [Task!]! ... } tasks: (_, args) => { if (args.done === undefined) return tasks; return tasks.filter(t => t.done === args.done); } 

✅ Exercise 3: Add a new field createdAt

Goal: each task has a timestamp.

Steps:

  1. Add createdAt: String! to Task type
  2. Set it when creating
👉 Click here to see the solution
 // Schema: type Task { createdAt: String! ... } createTask: (_, args) => { const title = args.title.trim(); if (!title) throw new Error("Title cannot be empty"); const newTask = { id: String(nextId++), title, done: false, createdAt: new Date().toISOString() }; tasks.push(newTask); return newTask; } 

✅ GraphQL Checklist

✅ Click to open the checklist
  • Server runs locally and prints the GraphQL URL
  • Schema defines Task, Query, and Mutation
  • query { tasks { ... } } works in the explorer
  • mutation { createTask(...) } creates a new item
  • mutation { setDone(...) } updates a task
  • Errors show meaningful messages (not vague)
  • Frontend fetch sends POST with { query, variables }
  • CORS is enabled for browser testing

❓ Mini Quiz

❓ What does the GraphQL schema represent?

A contract that defines available types and allowed operations.

❓ What is the difference between a Query and a Mutation?

Query reads data. Mutation changes data.

❓ What does String! mean in GraphQL?

The field is required and cannot be null.


💬 FAQ

Quick answers to common questions about GraphQL for beginners.

❓ Is GraphQL replacing REST?

No. REST is still widely used. GraphQL is an alternative that can be better for flexible data needs and complex UI screens.

❓ Why does GraphQL often return HTTP 200 even when there’s an error?

Because GraphQL treats the request as successfully processed, then reports field-level errors inside an errors array.

❓ Is GraphQL harder for beginners than REST?

It can be at first because you must learn schema + queries, but it becomes very clean once the mental model clicks.

❓ What should I learn after this GraphQL introduction?

Next: authentication, input validation patterns, pagination, and connecting resolvers to a real database.

❓ Can I use GraphQL from mobile apps?

Yes. Any client that can send HTTP requests can use GraphQL.



Leave a Reply

Your email address will not be published. Required fields are marked *