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
}
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
Three round trips to the server just to render one screen. On a mobile network, that cost is real.
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!
}
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
}
}
}
Response:
{
"data": {
"user": {
"name": "Anne",
"email": "anne@example.com",
"posts": [
{ "title": "My first post" },
{ "title": "Learning GraphQL" }
]
}
}
}
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
}
}
Response:
{
"data": {
"createUser": {
"id": "456",
"name": "Anne"
}
}
}
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
}
}
}
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)
}
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",
},
}
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 Explained for Beginners in 5 Minutes
- 📺 Learn GraphQL In 40 Minutes — Web Dev Simplified
- 📖 Official GraphQL Documentation
- 📖 gqlgen — Build GraphQL servers in Go
- 📖 How to GraphQL — Free fullstack tutorial
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)