# Part 1: Introduction to GraphQL and SDL

## How I Came to GraphQL

For years I built REST APIs and assumed the problem was just clients being needy. The mobile app wants a stripped-down user summary? Make a new endpoint. The dashboard needs data from three services? Make three sequential requests. I treated every extra endpoint as a product requirement rather than an API design problem.

The pivot happened while working on a platform that served both a React frontend and a mobile app. The REST API I had designed was correct by REST standards but neither client could use it efficiently. The web frontend was making six requests per page load; the mobile app was downloading 40 fields when it used three. I added projection parameters, created view-specific endpoints, and the API became harder to maintain with every sprint.

A colleague suggested GraphQL. I dismissed it at first — "it's just REST with extra steps." It is not. Once I understood that GraphQL moves the query responsibility to the client and the type system makes that safe, I rebuilt the API in GraphQL and the mobile team stopped asking for new endpoints entirely.

This part covers what GraphQL is, how it differs from REST, and how to read and write Schema Definition Language (SDL). By the end you will have a running Apollo Server with TypeScript.

## What is GraphQL?

GraphQL is three things:

1. **A query language for APIs** — clients describe what data they need in a structured syntax
2. **A type system** — the server defines all available data and operations in a schema written in SDL
3. **A runtime** — the server validates every incoming query against the schema and executes only what was asked

The [GraphQL specification](https://spec.graphql.org/) was open-sourced by Facebook in 2015. It is not tied to any database, transport, or language. Any server that implements the spec can be called a GraphQL server.

## Core Concepts

### The Schema is the Contract

In GraphQL, the schema is the single source of truth. It defines every type, every field, and every operation available. Unlike REST where the contract lives in documentation (OpenAPI, README files, or institutional memory), the GraphQL schema *is* the contract and is machine-readable.

```graphql
# The root query type — entry point for all read operations
type Query {
  user(id: ID!): User
  products(page: Int, pageSize: Int): ProductPage!
}

# The root mutation type — entry point for all write operations
type Mutation {
  createProduct(input: CreateProductInput!): Product!
  updateStock(productId: ID!, quantity: Int!): StockResult!
}

# The root subscription type — entry point for real-time events
type Subscription {
  stockChanged(productId: ID!): StockEvent!
}
```

### Clients Ask for Exactly What They Need

The defining behaviour of GraphQL: clients specify the exact fields they want, the server returns precisely those fields.

REST returns the full resource shape:

```json
GET /api/users/123
{
  "id": "123",
  "email": "user@example.com",
  "fullName": "...",
  "avatar": "...",
  "createdAt": "...",
  "lastLoginAt": "...",
  "roles": [...],
  "preferences": {...}
}
```

GraphQL returns only what was requested:

```graphql
query {
  user(id: "123") {
    email
    fullName
  }
}
```

```json
{
  "data": {
    "user": {
      "email": "user@example.com",
      "fullName": "..."
    }
  }
}
```

### Single Endpoint

All GraphQL operations go to a single endpoint — typically `POST /graphql`. The operation type (query, mutation, subscription) and the requested fields are in the request body, not the URL. This is a significant mental shift from REST.

## The Schema Definition Language (SDL)

SDL is the language used to define a GraphQL schema. It is human-readable, language-agnostic, and becomes the specification for your API. Every tool in the GraphQL ecosystem reads SDL.

### Scalar Types

GraphQL has five built-in scalar types:

| Scalar    | Description                                |
| --------- | ------------------------------------------ |
| `String`  | UTF-8 string                               |
| `Int`     | 32-bit signed integer                      |
| `Float`   | Double-precision floating point            |
| `Boolean` | `true` or `false`                          |
| `ID`      | Unique identifier — serialised as a string |

Custom scalars are covered in Part 2.

### Non-Null and Lists

By default, every field in GraphQL is nullable. The `!` suffix marks a field as non-null — the server guarantees it will never return `null` for that field.

```graphql
type User {
  id: ID!           # non-null: always returns a value
  email: String!    # non-null
  nickname: String  # nullable: may be null
  roles: [Role!]!   # non-null list of non-null Role items
  tags: [String]    # nullable list of nullable strings
}
```

Reading list nullability:

* `[Role!]!` — the list itself is non-null AND every item in the list is non-null
* `[String!]` — the list may be null, but if present, items are non-null
* `[String]` — both the list and its items may be null

### Object Types

```graphql
type Product {
  id: ID!
  name: String!
  price: Float!
  inStock: Boolean!
  stockCount: Int!
  category: Category
  variants: [ProductVariant!]!
  createdAt: String!
}
```

### Input Types

Input types are the only way to pass complex objects as arguments to mutations. Output types (e.g. `Product`) cannot be reused as inputs.

```graphql
input CreateProductInput {
  name: String!
  price: Float!
  categoryId: ID!
}

input UpdateProductInput {
  name: String
  price: Float
  categoryId: ID
}
```

Note that `UpdateProductInput` uses nullable fields — only the fields provided will be updated.

### Queries, Mutations, and Subscriptions

These three operation types map to the Query, Mutation, and Subscription root types:

```graphql
# Read — safe, idempotent (like HTTP GET)
type Query {
  product(id: ID!): Product
  searchProducts(query: String!, page: Int = 1): ProductPage!
}

# Write — may modify state (like HTTP POST/PUT/DELETE)
type Mutation {
  createProduct(input: CreateProductInput!): Product!
  deleteProduct(id: ID!): Boolean!
}

# Real-time — WebSocket-based push
type Subscription {
  productUpdated(id: ID!): Product!
}
```

### Arguments and Default Values

Fields can have arguments with optional default values:

```graphql
type Query {
  products(
    page: Int = 1
    pageSize: Int = 20
    sortBy: ProductSortField = CREATED_AT
    sortOrder: SortOrder = DESC
  ): ProductPage!
}

enum ProductSortField {
  CREATED_AT
  NAME
  PRICE
}

enum SortOrder {
  ASC
  DESC
}
```

## GraphQL vs REST vs gRPC

From personal experience, here is when I reach for each:

| Dimension           | GraphQL                                | REST                            | gRPC                                |
| ------------------- | -------------------------------------- | ------------------------------- | ----------------------------------- |
| Primary use         | Frontend-facing APIs, flexible queries | Standard CRUD APIs, public APIs | Internal microservice communication |
| Request shape       | Client-defined (query language)        | Server-defined (fixed response) | Strict schema (proto)               |
| Over-fetching       | Eliminated by design                   | Common                          | Eliminated by design                |
| Under-fetching      | Eliminated by design                   | Common                          | Eliminated by design                |
| Real-time           | Subscriptions (WebSocket)              | SSE / polling workaround        | Native streaming                    |
| Browser support     | Excellent (HTTP POST)                  | Excellent                       | Needs proxy (gRPC-web)              |
| Tooling maturity    | Excellent                              | Excellent                       | Good                                |
| Learning curve      | Medium                                 | Low                             | Medium-High                         |
| Inter-service calls | Adds overhead vs gRPC                  | Common, well-understood         | Best performance                    |

**When I choose GraphQL over REST**: frontend-facing APIs where multiple clients (web, mobile, public API) need different field subsets, or where the number of "combination" endpoints is growing uncontrollably.

**When I stick with REST**: simple CRUD with a single client, public webhooks, file upload/download, or when response caching at the CDN level is critical.

**When I use gRPC instead**: service-to-service internal communication where performance and strict contracts matter more than query flexibility.

## Setting Up Apollo Server 4 with TypeScript

Let me walk through the complete setup I use as a starting point.

### Project Initialisation

```bash
mkdir graphql-product-service
cd graphql-product-service
npm init -y
```

### package.json

```json
{
  "name": "graphql-product-service",
  "version": "1.0.0",
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js",
    "test": "jest --runInBand"
  },
  "dependencies": {
    "@apollo/server": "^4.10.0",
    "graphql": "^16.8.0",
    "graphql-tag": "^2.12.6",
    "graphql-ws": "^5.14.3",
    "dataloader": "^2.2.2",
    "jsonwebtoken": "^9.0.2",
    "zod": "^3.22.4",
    "winston": "^3.11.0",
    "ws": "^8.16.0"
  },
  "devDependencies": {
    "@types/jsonwebtoken": "^9.0.5",
    "@types/node": "^20.11.0",
    "@types/ws": "^8.5.10",
    "tsx": "^4.7.0",
    "typescript": "^5.3.3",
    "jest": "^29.7.0",
    "@types/jest": "^29.5.12",
    "ts-jest": "^29.1.2"
  }
}
```

```bash
npm install
```

### tsconfig.json

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
```

### Your First Schema and Server

```typescript
// src/schema.ts
import { gql } from 'graphql-tag';

export const typeDefs = gql`
  type Product {
    id: ID!
    name: String!
    price: Float!
    inStock: Boolean!
  }

  type Query {
    product(id: ID!): Product
    products: [Product!]!
  }
`;
```

```typescript
// src/resolvers.ts
import { Resolvers } from './types';  // generated in Part 2

// In-memory store for this introductory example
const products = [
  { id: '1', name: 'Widget Pro', price: 29.99, inStock: true },
  { id: '2', name: 'Gadget Lite', price: 14.50, inStock: false },
];

export const resolvers: Resolvers = {
  Query: {
    product: (_parent, { id }) => {
      return products.find(p => p.id === id) ?? null;
    },
    products: () => products,
  },
};
```

```typescript
// src/index.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { typeDefs } from './schema';
import { resolvers } from './resolvers';

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

  const { url } = await startStandaloneServer(server, {
    listen: { port: 4000 },
  });

  console.log(`GraphQL server ready at: ${url}`);
  console.log(`GraphQL Playground:      ${url}graphql`);
}

main().catch(console.error);
```

```bash
npm run dev
# GraphQL server ready at: http://localhost:4000/
```

### Running Your First Query

Open `http://localhost:4000` in a browser — Apollo Server serves a built-in sandbox where you can write and run queries interactively.

Or with `curl`:

```bash
curl -X POST http://localhost:4000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ products { id name price } }"}'
```

Response:

```json
{
  "data": {
    "products": [
      { "id": "1", "name": "Widget Pro", "price": 29.99 },
      { "id": "2", "name": "Gadget Lite", "price": 14.50 }
    ]
  }
}
```

## How GraphQL Executes a Query

Understanding execution helps debug unexpected resolver behaviour.

When the server receives:

```graphql
query {
  product(id: "1") {
    name
    price
  }
}
```

Execution proceeds field-by-field:

1. GraphQL validates the query against the schema (is `product` a valid query field? does it accept an `id` argument? are `name` and `price` valid fields on `Product`?)
2. The `Query.product` resolver runs with `args = { id: "1" }`
3. Its return value (`{ id: "1", name: "Widget Pro", price: 29.99, inStock: true }`) becomes the parent for the next level
4. The `Product.name` resolver runs — but since there is no custom resolver for `name`, GraphQL uses the **default resolver**: `parent.name`
5. Same for `Product.price`
6. `inStock` is in the parent object but was not requested — it is omitted from the response

The default resolver (`parent[fieldName]`) handles most scalar fields automatically. You only need to write custom resolvers when:

* The field name in GraphQL differs from the field name in the data source
* The field requires a database query (related entities)
* The field applies business logic before returning

## What's Next

You now understand:

* What GraphQL is and why it exists
* The SDL building blocks: scalars, types, inputs, enums, non-null, lists
* Queries, mutations, and subscriptions
* How GraphQL executes a query
* A running Apollo Server 4 with TypeScript

In [Part 2](https://blog.htunnthuthu.com/getting-started/programming/graphql-101/part-2-schema-design-type-system), we go deeper into the type system — interfaces, unions, custom scalars, and how to design a schema that models a real domain and evolves without versioning.
