DEV Community

Cover image for Getting Started with GraphQL
Lucy for Intuit Developers

Posted on

Getting Started with GraphQL

Welcome to the world of GraphQL! At its core, GraphQL is both a query language for your API and a server-side runtime for executing those queries. It uses a type system you define for your data. GraphQL isn’t just another tool—it represents a new way of thinking about how data is loaded and managed in applications. If you’re coming from a background in RESTful APIs, you are in for a treat.

Most of us are familiar with companies like Meta (Facebook), who developed GraphQL, or others who have adopted GraphQL to streamline and power their data interactions. Those companies include GitHub, Wayfair, Atlassian, and Intuit.

What makes GraphQL stand out? Ultimately, GraphQL gives you the ability to request exactly the data you need—nothing more and nothing less—and to retrieve that from a single endpoint.

In this guide, we’ll cover the essentials: what GraphQL is, how it differs from REST, and how to start integrating it into your projects. We’ll walk you through this step by step. Let’s begin with the question most asked by API developers who are new to GraphQL.

How Does GraphQL Differ from REST?

When working with APIs, you're probably used to REST, where each resource, like users or products, has its own URL. This setup can lead to a lot of endpoints, and sometimes, fetching simple information requires multiple requests. For instance, imagine you need to generate a summary of an order for a customer. You might need the date the order was placed and the names of the products in that order. With REST, this would typically involve:

  • Sending a request to the /orders endpoint to get the order dates.
  • Sending another request to the /products endpoint to fetch the product names.
  • Possibly sending another request to the /user endpoint to confirm customer details.

In each of these calls, REST might send back much more data than you need—like product prices, descriptions, or user addresses—which you won’t use for your summary.

Selective data retrieval
With GraphQL, you avoid this inefficiency by specifying exactly what you need: just the order date and product names, nothing more. This not only cuts down on the data transferred over the network but also significantly speeds up response times, making applications quicker and more responsive.

Single endpoint approach
Unlike REST, which uses multiple endpoints, GraphQL operates through a single endpoint. This simplifies the workflow because you don’t have to manage multiple URLs; everything happens in one place.

Efficiency and performance
One of the biggest advantages of using GraphQL is its efficiency. Since you can fetch all the required data in a single request, it reduces the number of requests sent to the server. This is particularly beneficial in complex systems or mobile applications where network performance is crucial.

Strongly typed schema
GraphQL uses a strongly typed system where each operation is backed by a schema defined on the server. This schema acts as a contract between the client and the server, ensuring only valid queries are allowed. It helps catch errors early in the development process, improving the quality of your code.

Understanding the Basics of GraphQL
As you start your journey with GraphQL, it’s important to grasp its fundamental concepts. These include the ways you can query data, modify it, and define the structure of data using GraphQL’s type system. Let’s break these down:

Core operations
At its heart, GraphQL is built around three main operations:

  • Queries: These are used to read or fetch data. Unlike REST, where you might receive more information than you requested, GraphQL queries are designed to return exactly what you specify, no more and no less. This precision not only makes data retrieval more efficient but also easier to predict and handle in your applications.
  • Mutations: While queries fetch data, mutations change data. Whether you’re adding new data, modifying existing data, or deleting it, mutations are your go-to operations. They are clearly defined in the schema, so you know exactly what kind of data modification is allowed and what isn’t.
  • Subscriptions: Although we won't delve deeply into subscriptions here, it's useful to know they exist for real-time data updates. Like listening to live updates from a server, subscriptions allow your application to get immediate data as soon as it changes on the server.

Type system
GraphQL’s type system is like a blueprint for your data. Here’s how it works:

  • Schema definition: This is where you define every type of data that can be queried or mutated through your GraphQL API. It outlines the structure of both input data (what you send) and output data (what you receive).
  • Strong typing: Every piece of data has a type, like String, Integer, or a custom type like User or Product. This strict typing helps catch errors during development, as GraphQL will only process queries and mutations that fit the schema.
  • Resolvers: For every field in your schema, there's a function called a resolver that's responsible for fetching the data for that field. This means whenever a query or mutation requests a specific field, the resolver for that field runs to retrieve or update the data.

Understanding these basics sets the foundation for effectively using GraphQL in your projects. You’ll find that with a strong grasp of queries, mutations, and the type system, transitioning from traditional REST approaches becomes much smoother and more intuitive.

Setting Up a GraphQL Environment
In this section, we’ll set up a GraphQL server to handle a dataset involving users, orders, and products. This will give you a practical feel for how GraphQL manages relationships and queries. We'll use Node.js for our server setup, and we’ll take advantage of a package from Apollo Server.

Apollo GraphQL is widely recognized as a leading provider of open-source tools for GraphQL. It offers a range of user-friendly solutions, such as Apollo Server and Apollo Studio, that help developers create and manage their GraphQL applications more efficiently. It has strong community support and straightforward documentation, making it easy for anyone to start working with GraphQL.

Installation and initial configuration
Whether you’re working in an IDE or directly from the command line, getting started with GraphQL simply requires that you have Node.js and npm installed on your machine. The easiest way to do this is through nvm (Node Version Manager), which you can use to install the latest version of Node.js and npm. You can follow these installation instructions for the simple steps to do this.

With Node.js and npm installed, you’re ready to begin working with GraphQL.

Step 1: Project initialization
First, we’ll start by creating our project directory and initializing it:

~$ mkdir project && cd project
~/project$ npm init -y
Enter fullscreen mode Exit fullscreen mode

Step 2: Install dependencies
Next, install Apollo Server and GraphQL:

~/project$ npm install apollo-server graphql
Enter fullscreen mode Exit fullscreen mode

Step 3: Data preparation
Prepare the data model with some dummy data by creating a file called data.json.

~/project$ touch data.json
Enter fullscreen mode Exit fullscreen mode

This file will contain a mock initial data set, which makes it easier to get up and running with GraphQL, as opposed to needing to set up an entire database backend. The data.json file should have the following contents:

{
  "users": [
    { "id": "1", "name": "Alice" },
    { "id": "2", "name": "Bob" }
  ],
  "orders": [
    { "id": "1", "userId": "1", "products": [{"productId": "1", "quantity": 1}, {"productId": "2", "quantity": 2}] },
    { "id": "2", "userId": "2", "products": [{"productId": "3", "quantity": 1}, {"productId": "4", "quantity": 1}] }
  ],
  "products": [
    { "id": "1", "name": "Laptop", "description": "High performance laptop", "price": 1249.99 },
    { "id": "2", "name": "Smartphone", "description": "Latest model smartphone", "price": 575.00 },
    { "id": "3", "name": "Tablet", "description": "Portable and powerful tablet", "price": 410.99 },
    { "id": "4", "name": "Headphones", "description": "Noise cancelling headphones", "price": 199.00 }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Server setup

Step 4: Define GraphQL schema and resolvers
In a server.js file, define our GraphQL schema (with Apollo Server, the convention is to define this in typeDefs), which includes types for User, Order, Product, and their relationships. Also, define the resolvers to specify how the data for each field in your schema is fetched from the data source.

~/project$ touch server.js
Enter fullscreen mode Exit fullscreen mode

The server.js file should look like this:

// server.js
const { ApolloServer, gql } = require('apollo-server');
const data = require('./data.json');

// Type definitions define the "shape" of your data and specify which ways the data can be fetched from the GraphQL server.
const typeDefs = gql`
  type User {
    id: ID
    name: String
    orders: [Order]
  }
  type Order {
    id: ID
    userId: ID
    products: [OrderProduct]
  }
  type OrderProduct {
    productId: ID
    quantity: Int
    product: Product
  }
  type Product {
    id: ID
    name: String
    description: String
    price: Float
  }
`;

// Resolvers define the technique for fetching the types defined in the schema.
const resolvers = {
  User: {
    orders: (user) => data.orders.filter(
        order => order.userId === user.id
    ),
  },
  OrderProduct: {
    product: (orderProduct) => data.products.find(
        product => product.id === orderProduct.productId
    ),
  }
};
Enter fullscreen mode Exit fullscreen mode

We’ve defined two resolvers above. Notice how in our typeDefs that a User has an array of Orders, and an Order has an array of OrderProducts. In our resolvers, we make it clear that a user’s orders are, naturally, those where the userId matches the user’s id. A similar approach applies to an Order and its OrderProducts. We use resolvers to help define the relationship between data models.

Step 5: Initialize and launch Apollo Server
Next, we initialize Apollo Server with the schema and resolvers, and then we start it up to listen for requests. Add the following to the bottom of server.js:

const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});
Enter fullscreen mode Exit fullscreen mode

This setup gives us a fully functional GraphQL server using Apollo. Next, we’ll dive into how to construct and execute queries to interact with this server setup.

Working with Queries
With our Apollo Server up and running, we can build out the typeDefs and resolvers to handle basic queries.

Define Query in typeDefs

Let’s add a Query type to our typeDefs.

const typeDefs = gql`
  type Query {
    users: [User]
    user(id: ID!): User
    orders: [Order]
    order(id: ID!): Order
    products: [Product]
    product(id: ID!): Product
  }
  type User {...}
  type Order {...}
  …
Enter fullscreen mode Exit fullscreen mode

These definitions establish the type of data that will be returned for each given query. For example, the users query will return an array of Users, while the user query—which will take a single id—will return a single User. Of course, it would make sense that the single User returned is the one with the matching id. However, we use resolvers to make that explicit.

Add Query to resolvers

Let’s update our resolvers object to look like this:

const resolvers = {
  Query: {
    users: () => data.users,
    user: (_, { id }) => data.users.find(user => user.id === id),
    orders: () => data.orders,
    order: (_, { id }) => data.orders.find(order => order.id === id),
    products: () => data.products,
    product: (_, { id }) => data.products.find(product => product.id === id),
  },
  User: { … },
  OrderProduct: { … }
};

Enter fullscreen mode Exit fullscreen mode

We see from how we define each Query in our resolvers that the users query will map to our data.users array. Meanwhile, a specific product query will search the data.products array for the product with the matching id.

Start server
Now, we’re ready to start our server. In the terminal, we execute the following command:

~/project$ node server.js
🚀 Server ready at http://localhost:4000/
Enter fullscreen mode Exit fullscreen mode

Test basic queries

To interact with our GraphQL API from the command line, we can use curl to send HTTP requests to the /graphql endpoint. If you were to visit http://localhost:4000 directly in your browser, you would see the Apollo Studio web interface (more on that below).

Let's say we want to fetch the name and orders for the user with id 1. The GraphQL query in JSON format would look like this:

~$ curl \
   -X POST \
   -H "Content-Type: application/json" \
   -d '{"query": "{ users { id name } }"}' \
   http://localhost:4000/graphql

{"data":{"users":[{"id":"1","name":"Alice"},{"id":"2","name":"Bob"}]}}
Enter fullscreen mode Exit fullscreen mode

In the above request, we are sending a users query, which we defined in our resolvers to be “all the users in the data.users array.” Based on the User schema defined in typeDefs, we can ask for an id, name, and orders for each User. However, in GraphQL, we get to decide how much information we want to receive. In the case of this request, we only want the id and name for each User.

Let’s send another request. This time, we will retrieve all the orders for a specific User, and for each Order, we also want to fetch certain information about the associated OrderProducts.

~$ curl \
   -X POST \
   -H "Content-Type: application/json" \
   -d '{"query": "{ user(id:1) { orders { id products { quantity product { name price } } } } }"}' \
   http://localhost:4000/graphql | json_pp

{
   "data" : {
      "user" : {
         "orders" : [
            {
               "id" : "1",
               "products" : [
                  {
                     "product" : {
                        "name" : "Laptop",
                        "price" : 1249.99
                     },
                     "quantity" : 1
                  },
                  {
                     "product" : {
                        "name" : "Smartphone",
                        "price" : 575
                     },
                     "quantity" : 2
                  }
               ]
            }
         ]
      }
   }
}
Enter fullscreen mode Exit fullscreen mode

Notice that both of our requests were POST requests to the same /graphql endpoint. All our requests will be POST requests, and they’ll all go to this endpoint. Our data payload notes that we are sending a query, and then we specify our query parameters as a JSON string.

Mutations and Updating Data

Mutations are essential for performing write operations in GraphQL. Unlike queries, which are used to fetch data, mutations change data—anything from creating and updating to deleting records. Let’s define some common mutations for your GraphQL API and show how to implement them.

Define Mutation in TypeDefs
First, we’ll extend our GraphQL schema to include mutation type definitions:

const typeDefs = gql`
  type Query { … }
  type Mutation {
    addUser(name: String!): User
    updateUser(id: ID!, name: String!): User
    addProduct(name: String!, description: String, price: Float!): Product
    addOrder(userId: ID!, products: [ProductInput!]!): Order
  }
  input ProductInput {
    productId: ID!
    quantity: Int!
  }
  type User { … }
  type Order { … }
  }
Enter fullscreen mode Exit fullscreen mode

We’re adding four mutations here:

  • addUser will add a new user with the given string name.
  • updateUser will update the name of the user with a given id.
  • addProduct will add a new product with the given name, description, and price.
  • addOrder will add a new order for a user with the given id, but it will also need to take an array of ProductInput objects, which we also define as a combination of a productId and quantity.

Add Mutation to resolvers
Next, we define resolvers for our mutations.

const resolvers = {
  Query: { ... },
  Mutation: {
    addUser: (_, { name }) => {
      const newUser = { id: String(data.users.length + 1), name };
      data.users.push(newUser);
      return newUser;
    },
    updateUser: (_, { id, name }) => {
      const user = data.users.find(user => user.id === id);
      if (!user) return null;
      user.name = name;
      return user;
    },
    addOrder: (_, { userId, products }) => {
      const newOrder = {
        id: String(data.orders.length + 1),
        userId,
        products
      };
      data.orders.push(newOrder);
      return newOrder;
    },
    addProduct: (_, { name, description, price }) => {
      const newProduct = { id: String(data.products.length + 1), name, description, price };
      data.products.push(newProduct);
      return newProduct;
    }
  },
  User: { ... },
  OrderProduct: { ... }
};
Enter fullscreen mode Exit fullscreen mode

In our resolvers, we specify how the Apollo Server should handle each of our mutation operations. This typically means creating a new object (or finding the object to be updated, in case of updateUser) and then updating data with that object.

Restart server
With our mutations defined, let’s restart our server with node server.js again.

Test mutation operations in Apollo Studio
Apollo has a convenient and feature-rich web interface called Apollo Explorer that can connect to our development environment server to make querying and testing easier. With our server started, we can visit the server URL (if you are running locally, then it will be http://localhost:4000) in our browser, and this will redirect us to Apollo Explorer. Alternatively, visit https://studio.apollographql.com/sandbox/explorer directly.

Image description

Let’s test out a few mutation operations in Apollo Explorer. For example, the following operation calls the addUser mutation to add a new user with the name Charlie, and then it returns the resulting id and name of the newly added user.

In Apollo Explorer, the operation looks like this:

Image description

What if we wanted to add a new order for Charlie? It would look like this:

Image description

Finally, here’s how we might perform an updateUser mutation:

Image description

Notice that mutation operations can be followed up with querying for more efficient operations.

A note on handling data
So far, our examples have used an in-memory object called data which started as data read in from a JSON file. This approach is great for learning and quick prototyping, but it isn’t suitable for production environments where data persistence is crucial.

In a production setting, you would typically connect your GraphQL server to a persistent database. The resolvers we've written, which currently manipulate the in-memory data object, would be adjusted to execute queries against this database instead.

Error Handling and Debugging
Handling errors and debugging in GraphQL may differ slightly from how you would do it with traditional REST APIs. Here are some tips for approaching error handling and debugging efficiently:

  • Check for errors: GraphQL operations typically return a list of errors in the response body. These might not be HTTP status errors but are instead part of the GraphQL response under the errors field. Therefore, you should always check for errors in the response alongside the data to ensure comprehensive error handling.
  • Logging: Implement logging on the server side to capture requests and errors. This can help you trace issues back to specific queries or mutations.
  • Validate queries: Use tools like Apollo Explorer during development to validate queries and mutations for syntax and structure before deployment.
  • Use clear error messages: Design your resolvers to throw errors which include clear and descriptive error messages. This helps clients understand what went wrong without the need to dig into server-side logs.

Tools and Ecosystem

When working with GraphQL—especially as you’re just starting out—you don’t need to build everything from scratch. Take advantage of the rich ecosystem of tools designed to enhance your development experience. Apollo Studio featuring Explorer is a prime example, allowing you to build and test your queries interactively within a web interface. This makes it easier to visualize and manipulate your GraphQL operations.

For an alternative in-browser GraphQL IDEs, there is also GraphiQL. For API testing and query building, Postman—traditionally known for handling REST APIs—also supports GraphQL. Additionally, GraphQL Voyager is a tool for visually exploring your GraphQL API as an interactive graph. This can be particularly useful for understanding and documenting the relationships between entities.

Conclusion

Congratulations on navigating through the fundamentals of GraphQL! By now, you should have a solid understanding of how GraphQL differs from traditional REST APIs, its efficiency in fetching and manipulating data, and the flexibility it offers through queries and mutations.

Throughout this guide, we've explored:

  • Setting up a GraphQL environment using Apollo Server.
  • Constructing and executing queries to retrieve data, demonstrating the efficiency of GraphQL in fetching exactly only the data that you need.
  • Implementing mutations to modify data.
  • Tips for error handling and debugging.
  • Additional tools that can help you in your GraphQL learning journey.

Continue exploring GraphQL, and spend time looking into advanced features like subscriptions. As you design APIs for projects in the future, consider whether the efficiency and power of GraphQL may be a better fit for your needs than a traditional REST API. Keep learning, keep building, and make the most of what GraphQL has to offer!

Image description

Top comments (2)

Collapse
 
big89 profile image
Bhagwan Sahane

Very detailed and nice resource to start with GraphQL.

Collapse
 
technvision profile image
Sohil Ahmed

Can you make a SQL server as your database while using GraphQL, please make a nlog post about it.