Douglas Ochner

GraphQL First Steps - A Practical Getting Started Guide

Dec 15, 2024 · 15min

GraphQL is a powerful query language for APIs that allows clients to request exactly the data they need. Unlike traditional REST APIs where you might need multiple endpoints or receive more data than necessary, GraphQL provides a more efficient and flexible approach to data fetching.

In this guide, we’ll walk through the fundamentals of GraphQL and build a simple API from scratch.

What is GraphQL?

GraphQL was developed by Facebook in 2012 and open-sourced in 2015. It’s a query language that lets you:

  • Request exactly what you need - No more over-fetching or under-fetching data
  • Get multiple resources in a single request - Reduce the number of network calls
  • Strongly typed schema - Self-documenting APIs with built-in validation
  • Faster development - Frontend and backend teams can work independently

GraphQL vs REST

Here’s a quick comparison:

FeatureRESTGraphQL
Data fetchingMultiple endpointsSingle endpoint
Over/under-fetchingCommon issueSolve by design
VersioningURL versioning (/v1, /v2)No versioning needed
DocumentationSwagger/OpenAPIBuilt-in (GraphiQL)

Core Concepts

1. Schema

The schema defines your API structure using the GraphQL Schema Definition Language (SDL):

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

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

Key points:

  • ! means the field is required (non-nullable)
  • [Post!]! means a non-null array of non-null Posts
  • Types define the shape of your data

2. Queries

Queries are how clients request data:

query GetUser {
  user(id: "1") {
    name
    email
    posts {
      title
    }
  }
}

Response:

{
  "data": {
    "user": {
      "name": "Douglas Ochner",
      "email": "douglas.ochner@gmail.com",
      "posts": [
        { "title": "GraphQL First Steps" },
        { "title": "Vue 3 with TypeScript" }
      ]
    }
  }
}

3. Mutations

Mutations modify data (similar to POST/PUT/DELETE in REST):

mutation CreatePost {
  createPost(
    title: "My First Post"
    content: "Learning GraphQL is awesome!"
  ) {
    id
    title
    author {
      name
    }
  }
}

4. Resolvers

Resolvers are functions that fetch the data for each field:

const resolvers = {
  Query: {
    user: (parent, args, context) => {
      return getUserById(args.id)
    }
  },
  User: {
    posts: (user) => {
      return getPostsByUserId(user.id)
    }
  }
}

Building Your First GraphQL API

Let’s build a simple GraphQL server with Node.js and Apollo Server.

Step 1: Setup

# Create new project
mkdir graphql-demo
cd graphql-demo
npm init -y

# Install dependencies
npm install @apollo/server graphql

Step 2: Define Your Schema

Create schema.js:

export const typeDefs = `#graphql
  type Book {
    id: ID!
    title: String!
    author: String!
    year: Int
  }

  type Query {
    books: [Book!]!
    book(id: ID!): Book
  }

  type Mutation {
    addBook(title: String!, author: String!, year: Int): Book!
    deleteBook(id: ID!): Boolean!
  }
`

Step 3: Create Resolvers

Create resolvers.js:

// Mock database
let books = [
  { id: '1', title: 'Clean Code', author: 'Robert Martin', year: 2008 },
  { id: '2', title: 'The Pragmatic Programmer', author: 'Hunt & Thomas', year: 1999 }
]

export const resolvers = {
  Query: {
    books: () => books,
    book: (_, { id }) => books.find(book => book.id === id)
  },

  Mutation: {
    addBook: (_, { title, author, year }) => {
      const newBook = {
        id: String(books.length + 1),
        title,
        author,
        year
      }
      books.push(newBook)
      return newBook
    },

    deleteBook: (_, { id }) => {
      const index = books.findIndex(book => book.id === id)
      if (index === -1) return false
      books.splice(index, 1)
      return true
    }
  }
}

Step 4: Create the Server

Create server.js:

import { ApolloServer } from '@apollo/server'
import { startStandaloneServer } from '@apollo/server/standalone'
import { typeDefs } from './schema.js'
import { resolvers } from './resolvers.js'

const server = new ApolloServer({
  typeDefs,
  resolvers,
})

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

console.log(`🚀 Server ready at: ${url}`)

Step 5: Update package.json

Add type module:

{
  "type": "module",
  "scripts": {
    "dev": "node server.js"
  }
}

Step 6: Run and Test

npm run dev

Visit http://localhost:4000 to access Apollo Studio (built-in GraphQL IDE).

Testing Your API

Query All Books

query GetAllBooks {
  books {
    id
    title
    author
    year
  }
}

Query Single Book

query GetBook {
  book(id: "1") {
    title
    author
  }
}

Add a New Book

mutation AddBook {
  addBook(
    title: "You Don't Know JS"
    author: "Kyle Simpson"
    year: 2014
  ) {
    id
    title
    author
  }
}

Delete a Book

mutation DeleteBook {
  deleteBook(id: "2")
}

Frontend Integration

Using Apollo Client (React/Vue)

Install Apollo Client:

npm install @apollo/client graphql

Vue 3 Example:

import { ApolloClient, InMemoryCache, gql } from '@apollo/client/core'

const client = new ApolloClient({
  uri: 'http://localhost:4000',
  cache: new InMemoryCache()
})

// Query books
const GET_BOOKS = gql`
  query GetBooks {
    books {
      id
      title
      author
    }
  }
`

const { data } = await client.query({ query: GET_BOOKS })
console.log(data.books)

Using Fetch API

const query = `
  query GetBooks {
    books {
      id
      title
      author
    }
  }
`

const response = await fetch('http://localhost:4000', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ query })
})

const { data } = await response.json()
console.log(data.books)

Best Practices

1. Schema Design

✅ DO:

  • Use descriptive names
  • Keep types focused and single-purpose
  • Use non-null (!) appropriately
  • Document with descriptions
"""
Represents a user in the system
"""
type User {
  "Unique identifier"
  id: ID!
  "User's full name"
  name: String!
}

❌ DON’T:

  • Create overly nested types
  • Use generic names (e.g., Data, Info)
  • Make everything nullable

2. Error Handling

const resolvers = {
  Query: {
    book: (_, { id }) => {
      const book = books.find(b => b.id === id)
      if (!book) {
        throw new GraphQLError('Book not found', {
          extensions: {
            code: 'NOT_FOUND',
            id
          }
        })
      }
      return book
    }
  }
}

3. Performance Optimization

Use DataLoader to prevent N+1 queries:

import DataLoader from 'dataloader'

const bookLoader = new DataLoader(async (userIds) => {
  const books = await fetchBooksByUserIds(userIds)
  return userIds.map(id => books.filter(b => b.userId === id))
})

const resolvers = {
  User: {
    books: (user) => bookLoader.load(user.id)
  }
}

4. Security

  • Implement depth limiting to prevent deeply nested queries
  • Query complexity analysis to prevent expensive operations
  • Rate limiting to prevent abuse
  • Authentication & Authorization
const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(5)],
  context: ({ req }) => {
    const token = req.headers.authorization || ''
    const user = verifyToken(token)
    return { user }
  }
})

Common Patterns

Pagination

type Query {
  books(limit: Int, offset: Int): BookConnection!
}

type BookConnection {
  edges: [BookEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type BookEdge {
  node: Book!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

Filtering

input BookFilter {
  author: String
  yearFrom: Int
  yearTo: Int
}

type Query {
  books(filter: BookFilter): [Book!]!
}

Next Steps

Now that you understand GraphQL basics, here are some advanced topics to explore:

  1. Subscriptions - Real-time data with WebSockets
  2. File uploads - Handle multipart form data
  3. Batch operations - Optimize multiple mutations
  4. Federation - Combine multiple GraphQL services
  5. Code generation - Auto-generate TypeScript types

Resources

Conclusion

GraphQL provides a modern, efficient approach to building APIs. Its strong typing, flexible querying, and excellent developer experience make it a great choice for modern applications.

Key takeaways:

  • ✅ Single endpoint for all data operations
  • ✅ Request exactly what you need
  • ✅ Strongly typed schema with built-in documentation
  • ✅ Perfect for frontend-driven development
  • ✅ Great ecosystem and tooling

Start small, experiment with queries and mutations, and gradually explore more advanced features as your needs grow.

Happy coding! 🚀


Have questions about GraphQL or want to discuss API architecture? Feel free to reach out!

>
CC BY-NC-SA 4.0 2020-PRESENT © Douglas Ochner