DEV Community

Cover image for What Is GraphQL?
Mohamed Elmorsy
Mohamed Elmorsy

Posted on

What Is GraphQL?

You've been building REST APIs — one endpoint for users, another for posts, another for comments. The client makes three requests, stitches the data together, and half of it gets thrown away because it wasn't needed in the first place.

GraphQL was built to fix exactly that. It gives the client full control over what data it receives. One request. Exactly what you asked for. Nothing more, nothing less.


The Problem REST Couldn't Solve

Before understanding GraphQL, you need to understand the two problems that drove its creation.

Over-fetching

The server returns more data than the client needs.

GET /users/123

Response:
{
  "id": 123,
  "name": "Anne",
  "email": "anne@example.com",
  "phone": "...",
  "address": "...",
  "createdAt": "...",   ← you didn't need any of this
  "updatedAt": "..."    ← but the server sent it anyway
}
Enter fullscreen mode Exit fullscreen mode

The client only needed name and email — but it downloaded the whole object every time.

Under-fetching

One endpoint doesn't return enough, so the client has to make multiple requests.

GET /users/123          → gets the user
GET /users/123/posts    → gets their posts
GET /users/123/followers → gets their followers
Enter fullscreen mode Exit fullscreen mode

Three round trips to the server just to render one screen. On a mobile network, that cost is real.

GraphQL vs REST over-fetching and under-fetching

GraphQL's answer: Let the client write the query. The server returns exactly what was asked.


What Is GraphQL?

GraphQL is a query language for your API and a runtime for executing those queries. It was created by Facebook in 2012, open-sourced in 2015, and is now maintained by the GraphQL Foundation.

Unlike REST, which exposes multiple URL endpoints, GraphQL exposes a single endpoint — typically POST /graphql. The client sends a query in the request body describing exactly what it wants, and the server responds with only that data.

Key characteristics:

  • Single endpoint — everything goes through POST /graphql
  • Client-driven — the client defines the shape of the response
  • Strongly typed — every field has a declared type in the schema
  • Introspective — clients can query the API to discover its own structure

Core Concepts

1. Schema

The schema is the contract between client and server. It defines every type, every field, and every operation the API supports. It is written in SDL (Schema Definition Language):

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  body: String!
  author: User!
}

type Query {
  user(id: ID!): User
  posts: [Post!]!
}

type Mutation {
  createUser(name: String!, email: String!): User!
  deleteUser(id: ID!): Boolean!
}
Enter fullscreen mode Exit fullscreen mode

The ! means the field is non-nullable — it will never return null.


2. Query

A Query is a read operation — the equivalent of GET in REST. The client specifies exactly which fields it wants:

query {
  user(id: "123") {
    name
    email
    posts {
      title
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "data": {
    "user": {
      "name": "Anne",
      "email": "anne@example.com",
      "posts": [
        { "title": "My first post" },
        { "title": "Learning GraphQL" }
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

One request. Nested data. Only the fields that were asked for.


3. Mutation

A Mutation is a write operation — the equivalent of POST, PUT, or DELETE in REST:

mutation {
  createUser(name: "Anne", email: "anne@example.com") {
    id
    name
  }
}
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "data": {
    "createUser": {
      "id": "456",
      "name": "Anne"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Subscription

A Subscription is a real-time operation. The client subscribes to an event, and the server pushes data whenever that event occurs — powered by WebSockets under the hood:

subscription {
  newMessage(roomId: "general") {
    content
    sender {
      name
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The server pushes a new message to every subscribed client as soon as it is sent.


GraphQL vs REST

Feature REST GraphQL
Endpoints Multiple (/users, /posts, ...) Single (/graphql)
Data shape Fixed by the server Defined by the client
Over-fetching Common Eliminated
Under-fetching Common Eliminated
Versioning /v1/, /v2/ Schema evolution via deprecation
Type system None (unless OpenAPI) Built-in, strongly typed
Real-time Requires polling or SSE Native via Subscriptions
Learning curve Low Medium
Best for Simple CRUD APIs Complex, nested, or client-diverse data

Using GraphQL in Go

GraphQL over HTTP is just a POST request with a JSON body — no special library needed for the client side.

Sending a GraphQL Query

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type GraphQLRequest struct {
    Query     string         `json:"query"`
    Variables map[string]any `json:"variables,omitempty"`
}

type GraphQLResponse struct {
    Data   map[string]any `json:"data"`
    Errors []struct {
        Message string `json:"message"`
    } `json:"errors,omitempty"`
}

func main() {
    url := "https://api.example.com/graphql"

    // 1. Build the request body
    gqlReq := GraphQLRequest{
        Query: `
            query GetUser($id: ID!) {
                user(id: $id) {
                    name
                    email
                    posts {
                        title
                    }
                }
            }
        `,
        Variables: map[string]any{
            "id": "123",
        },
    }

    body, err := json.Marshal(gqlReq)
    if err != nil {
        panic(err)
    }

    // 2. Send the POST request
    resp, err := http.Post(url, "application/json", bytes.NewBuffer(body))
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    // 3. Read and decode the response
    raw, _ := io.ReadAll(resp.Body)

    var gqlResp GraphQLResponse
    if err := json.Unmarshal(raw, &gqlResp); err != nil {
        panic(err)
    }

    // 4. Check for GraphQL-level errors
    if len(gqlResp.Errors) > 0 {
        fmt.Printf("GraphQL Error: %s\n", gqlResp.Errors[0].Message)
        return
    }

    fmt.Printf("Response: %+v\n", gqlResp.Data)
}
Enter fullscreen mode Exit fullscreen mode

Sending a Mutation

gqlReq := GraphQLRequest{
    Query: `
        mutation CreateUser($name: String!, $email: String!) {
            createUser(name: $name, email: $email) {
                id
                name
            }
        }
    `,
    Variables: map[string]any{
        "name":  "Anne",
        "email": "anne@example.com",
    },
}
Enter fullscreen mode Exit fullscreen mode

Building a GraphQL server in Go? The most popular library is gqlgen — it generates type-safe Go code directly from your schema. Install it with go get github.com/99designs/gqlgen.


Summary

Concept What It Is
GraphQL A query language and runtime for APIs
Schema The typed contract defining all available data and operations
Query A read operation — asks for specific fields
Mutation A write operation — creates, updates, or deletes data
Subscription A real-time operation — server pushes events to the client
Single endpoint All operations go through one URL (POST /graphql)

Further Reading & Watch


GraphQL solves the data-fetching problem beautifully — the client asks, the server delivers. But notice something: everything above is still request/response. The client asks, waits, gets an answer. Even subscriptions require a special setup.

What if you needed a persistent, two-way channel where the server can push data to the client at any time, without the client asking first? That is where REST and GraphQL both step aside and let WebSockets take over. Stay tuned.

Top comments (0)