30 Days of JavaScript: GraphQL and JavaScript, Day 23

30 Days of JavaScript: Building a Simple Weather App
30 Days of JavaScript: GraphQL and JavaScript, Day 23

Learn GraphQL basics and how to set up Apollo Client. Create efficient queries and mutations, and integrate GraphQL seamlessly into your JavaScript projects.

You need a user’s name and email. The REST endpoint hands you back the whole user object: forty fields, three of which you’ll ever read. Do that on a slow phone connection and the waste has a real cost. GraphQL is the fix for exactly that: you ask for the fields you want, and that is all that comes back.

This is Day 23 of our 30-day JavaScript journey. Today we get GraphQL working from a JavaScript client. We’ll cover the shape of the language, wire up Apollo Client, run queries and mutations, then touch subscriptions, fragments, error handling, and caching. By the end you’ll be able to drop GraphQL into a real project without guessing.

Table of Contents

Introduction to GraphQL

What is GraphQL?

GraphQL is an open-source query language for APIs, and a runtime that answers those queries against your existing data. Facebook built it in 2012 and open-sourced it in 2015; it now lives under the GraphQL Foundation. The core idea: instead of many fixed endpoints that each return a fixed shape, you get one endpoint and a strongly typed schema, and the client decides which fields it wants.

Advantages of Using GraphQL

Compared with a traditional REST API, GraphQL buys you a few concrete things:

  • Precise Data Retrieval: Clients can request only the data they need, minimizing over-fetching.
  • Strongly Typed Schema: Ensures data is consistent and type-safe.
  • Real-Time Data: Supports real-time updates through subscriptions.
Core Concepts of GraphQL

Four terms cover most of what you’ll touch day to day:

  • Schema: Defines the structure and types of data available in the API.
  • Queries: Used to request data from the server.
  • Mutations: Used to modify server data and return the result.
  • Resolvers: Functions that handle the logic for fetching and processing the data.

Setting Up Apollo Client

Introduction to Apollo Client

Apollo Client is the JavaScript library most teams reach for when they talk to a GraphQL API. It runs the network requests, caches results, and gives your components hooks for React (with adapters for other frameworks), so you’re not managing fetch state by hand.

Installing Apollo Client

Install it alongside the graphql package:

Step 1: Install Apollo Client and GraphQL

Bash
npm install @apollo/client graphql

Step 2: Setting Up Apollo Provider

Wrap your root component in ApolloProvider so every component below it can reach the client.

JS
import React from "react";
import ReactDOM from "react-dom";
import { ApolloProvider, InMemoryCache, ApolloClient } from "@apollo/client";
const client = new ApolloClient({
  uri: "https://your-graphql-endpoint.com/graphql",
  cache: new InMemoryCache()
});
ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  document.getElementById("root")
);

One note before you copy this: the snippet uses ReactDOM.render, the pre-React 18 API. On React 18 and up you’d create a root with createRoot instead. The Apollo wiring is identical either way, and it connects your app to the GraphQL server so it’s ready to run queries and mutations.

Creating Your First Query

Here’s a query that pulls a list of users and renders them.

JS
/**
 * Retrieves a list of users from the server.
 *
 * @returns {JSX.Element} A component that displays a list of users.
 */
import { gql, useQuery } from "@apollo/client";
const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`;
function Users() {
  const { loading, error, data } = useQuery(GET_USERS);
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  return (
    <ul>
      {data.users.map(user => (
        <li key={user.id}>
          {user.name} - {user.email}
        </li>
      ))}
    </ul>
  );
}
export default Users;

The useQuery hook runs the query and hands you loading, error, and data, so you can handle each state without extra plumbing.

Making GraphQL Queries and Mutations

Writing GraphQL Queries

A query spells out exactly which fields you want, and the response comes back in that same shape.

JS
/**
 * Fetches a specific post by ID with title, content, and author information.
 *
 * @param {ID} id - The ID of the post to fetch.
 */
const GET_POST = gql`
  query GetPost($id: ID!) {
    post(id: $id) {
      title
      content
      author {
        name
      }
    }
  }
`;

This one asks for a single post by its ID and pulls the title, content, and author’s name in the same round trip.

Creating GraphQL Mutations

Mutations are how you change data: create, update, delete. Same syntax as a query, different intent.

JS
/**
 * Mutation to add a new post with title, content, and author ID.
 *
 * @param {String} title - Title of the post.
 * @param {String} content - Content of the post.
 * @param {ID} authorId - ID of the author.
 */
const ADD_POST = gql`
  mutation AddPost($title: String!, $content: String!, $authorId: ID!) {
    addPost(title: $title, content: $content, authorId: $authorId) {
      id
      title
      content
    }
  }
`;

It creates a post and returns the fields you asked for on the way out, here the new id, title, and content.

Handling Variables in GraphQL

Variables keep a query or mutation reusable. You declare them in the operation, then pass values at call time.

JS
/**
 * Form component to add a new post using GraphQL mutation.
 *
 * @returns {JSX.Element} A form to add a new post.
 */
const ADD_POST = gql`
  mutation AddPost($title: String!, $content: String!, $authorId: ID!) {
    addPost(title: $title, content: $content, authorId: $authorId) {
      id
      title
    }
  }
`;
function NewPostForm() {
  const [addPost] = useMutation(ADD_POST);
  const handleSubmit = (e) => {
    e.preventDefault();
    const title = e.target.title.value;
    const content = e.target.content.value;
    const authorId = e.target.authorId.value;
    addPost({ variables: { title, content, authorId } });
  };
  return (
    <form onSubmit={handleSubmit}>
      <input name="title" placeholder="Title" />
      <textarea name="content" placeholder="Content" />
      <input name="authorId" placeholder="Author ID" />
      <button type="submit">Add Post</button>
    </form>
  );
}

The form reads its inputs and passes them to addPost as variables, so the same mutation handles every submission.

Combining Queries and Mutations in a Project

Real screens read and write. Here’s a small to-do app that does both.

JS
/**
 * TodoApp component that combines query and mutation to display and add tasks.
 *
 * @returns {JSX.Element} A to-do application with a form and task list.
 */
import { gql, useQuery, useMutation } from "@apollo/client";
const GET_TODOS = gql`
  query GetTodos {
    todos {
      id
      task
      completed
    }
  }
`;
const ADD_TODO = gql`
  mutation AddTodo($task: String!) {
    addTodo(task: $task) {
      id
      task
      completed
    }
  }
`;
function TodoApp() {
  const { loading, error, data } = useQuery(GET_TODOS);
  const [addTodo] = useMutation(ADD_TODO, {
    refetchQueries: [{ query: GET_TODOS }],
  });
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;
  const handleAddTodo = (e) => {
    e.preventDefault();
    const task = e.target.task.value;
    addTodo({ variables: { task } });
    e.target.reset();
  };
  return (
    <div>
      <form onSubmit={handleAddTodo}>
        <input name="task" placeholder="New Task" />
        <button type="submit">Add Todo</button>
      </form>
      <ul>
        {data.todos.map(todo => (
          <li key={todo.id}>
            {todo.task} {todo.completed ? "(Completed)" : "(Incomplete)"}
          </li>
        ))}
      </ul>
    </div>
  );
}
export default TodoApp;

The refetchQueries option re-runs GET_TODOS after a new task is added, so the list stays in sync with the server without a manual refresh.

Advanced GraphQL Features

Implementing Subscriptions

Subscriptions hold an open connection and push new data to the client as it changes, which is how you get live updates without polling the server.

JS
/**
 * MessageList component that subscribes to new messages in real-time.
 *
 * @returns {JSX.Element} A list of messages updated in real-time.
 */
import { gql, useSubscription } from "@apollo/client";
const MESSAGE_ADDED = gql`
  subscription OnMessageAdded {
    messageAdded {
      id
      content
      author {
        name
      }
    }
  }
`;
function MessageList() {
  const { data, loading } = useSubscription(MESSAGE_ADDED);
  if (loading) return <p>Loading...</p>;
  return (
    <ul>
      {data.messageAdded.map(message => (
        <li key={message.id}>
          {message.author.name}: {message.content}
        </li>
      ))}
    </ul>
  );
}

The useSubscription hook wires the component to that stream, so new messages show up in the UI as they arrive instead of on the next refresh.

Optimizing GraphQL Queries with Fragments

A fragment is a named, reusable set of fields. Define the shape once, then spread it into any query that needs it.

JS
/**
 * GraphQL fragment for reusable post details.
 */
const POST_DETAILS = gql`
  fragment PostDetails on Post {
    id
    title
    content
  }
`;
const GET_POSTS = gql`
  query GetPosts {
    posts {
      ...PostDetails
      author {
        name
      }
    }
  }
`;
const GET_POST = gql`
  query GetPost($id: ID!) {
    post(id: $id) {
      ...PostDetails
      author {
        name
      }
    }
  }
`;

Here PostDetails feeds both GET_POSTS and GET_POST, so the field list lives in one place instead of two.

Error Handling and Caching

Managing Errors in GraphQL

Two kinds of errors show up: network failures, and GraphQL errors the server returns inside the response. Apollo surfaces both through the same error value.

JS
const { loading, error, data } = useQuery(GET_USERS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;

Checking loading and error before you touch data keeps the UI honest about what’s happening under the hood.

Implementing Caching Strategies

Apollo stores query results in a normalized cache, so a repeat request for data it already holds can be served locally instead of hitting the network again.

JS
const client = new ApolloClient({
  uri: "https://your-graphql-endpoint.com/graphql",
  cache: new InMemoryCache()
});

Passing new InMemoryCache() when you create the client is all it takes to turn that on.

Conclusion

That’s GraphQL from the client side: one typed endpoint, queries that return exactly the fields you name, mutations that change data, and subscriptions for the live stuff. Apollo carries the wiring, the cache, and the loading and error states, so you can stay on the UI.

The honest trade: GraphQL is not free. You take on a schema, a server that resolves it, and a client library, which is real weight for a small app. On anything with a lot of related data and clients that each want a different slice of it, that weight pays for itself fast.

What’s Next?

Day 24 is Progressive Web Apps: building a JavaScript app that works offline and feels closer to a native one. We’ll get into service workers, caching, and performance. See you there.

Next: 30 Days of JavaScript: Progressive Web Apps (PWAs), Day 24

Leave a Comment

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


Scroll to Top