Building Your First GraphQL Schema

In the previous installment of this series, we covered the fundamentals of GraphQL as a data manipulation and query language for APIs. Its two main advantages: you describe the exact structure of the response you want, and all resource requests go through a single endpoint. Now we move from theory to practice by building object and root fields against a sample Ruby on Rails application.

This tutorial uses a food-themed app. Clone the companion repository and seed the database. The project comes with two models: Food and Nutrition, along with the required GraphQL gems.

Attribute Type
id Bigint
Name String
place_of_origin String
image String
created_At Timestamp
updated_at Timestamp
Attribute Type
id Bigint
food_id Bigint
serving_size String
calories String
total_fat String
trans_fat String
saturated_fat String
cholesterol String
sodium String
Potassium String
total_carbohydrate String
dietary_fiber String
sugars String
protein String
vitamin_a String
vitamin_c String
calciuum String
iron String
created_at Timestamp
update_at Timestamp

Understanding the GraphQL Directory Layout

All GraphQL-related code lives under /app/graphql. Opening this folder in your editor reveals two key directories and one schema file:

A screenshot of the directory structure of the folder graphql in an IDE. Under the top folder graphql is mutations and types and they are surrounded by a yellow box.  Underneath them is foo_app_schema.rb.
Directory structure of the folder graphql
  • Mutations – classes that modify data (create, update, delete).
  • Types – classes defining what gets returned, including the entry-point query classes query_type.rb and mutation_type.rb.
A screenshot of the directory structure of the folder graphql in an IDE. Under the top folder graphql is mutations and types. Underneath them is foo_app_schema.rb which is surrounded by a red box.
Directory structure of the folder graphql

The file food_app_schema.rb declares which queries are available on the server.

Creating the First Query: all_food

Before writing queries, it helps to distinguish the two kinds of fields in GraphQL:

  1. Root fields are entry points to the server. They define the shape of the response based on selected object fields, analogous to REST endpoints.
  2. Object fields are the individual attributes on a given object.

To expose data, first create an object type. Run the generator:

rails g graphql:object food

This produces food_type.rb, populated with every column from the foods table in db/schema.rb. The resulting class holds all the object fields for that resource.

Next, define the root field. Open query_type.rb—the class that collects all root fields—and replace the default test_field with:

field :all_food, [Types::FoodType], null: false, description: "Get all the food items."

The field helper takes four parameters: the field name (:all_food), the return type ([Types::FoodType], a list), a null flag indicating whether the field can ever return null, and a human-readable description.

Running Queries in GraphiQL

The project ships with GraphiQL, an interactive playground. Navigate to localhost:3000/graphiql in your browser. The left pane is for writing queries; the right pane displays the response.

 A screenshot of the GraphiQL playground.  There are two large text boxes side by side. The left text box is editable and the right isn't. The menu item at the top shows the GraphiQL name, a play button, Prettify button, and History button.
GraphiQL playground

The toolbar includes buttons to execute a query, prettify the query text, and browse your query history. The < Docs button in the top-right is particularly useful: it opens a schema explorer listing every available query and mutation.

A  screenshot of the navigation menu of the GraphiQL. The menu item shows the a play button, a Prettify button, and a History button.
GrapiQL playground menus
A screenshot of the <Docs menu item. There is a large red arrow pointing to the menu item and it says click here.
Docs menu item

Clicking query: Query reveals the all_food field just defined. Selecting [Food!]! shows all the object fields that can be requested for each food item. Because GraphQL returns only what you ask for, you can request a subset of attributes. To fetch just IDs and names:

query {
  allFood {
    id
    name
  }
}

Executing that query returns the matching fields for every row. To retrieve additional attributes, add them to the query body—for example, image and placeOfOrigin.

What Happens Behind the Scenes

GraphQL consolidates all RESTful actions into a single smart endpoint. When you run a query, the request flow is:

A flow diagram showing the steps to execute a query between the client and the food app's server.
  1. You submit a request with the query and any variables to the GraphQL endpoint.
  2. The endpoint’s graphql_controller invokes the execute method to process the request.
  3. The method renders a JSON response tailored to your specific query.
  4. The response is returned to your client.

Practice: Building a nutrition Query

Try implementing a root field named nutrition that returns all nutrition facts, mirroring the all_food pattern. A sample query and response are available in a reference gist.

Adding Nested Object Fields

The Nutrition table contains a foreign key linking each food item to a single nutrition fact. The association exists at the model layer, but GraphQL doesn’t know about it yet. To allow querying a food item together with its nutrition data, add the field to food_type.rb:

field :nutrition, Types::NutritionType, null: true

Now a query can request both food attributes and nested nutrition attributes, such as:

query {
  allFood {
    name
    nutrition {
      servingSize
      calories
    }
  }
}

The response nests the nutrition facts under each food item.

Querying with Arguments

Returning entire tables is useful, but often you need a single record. Add a root field that takes an id argument:

field :food, Types::FoodType, null: false do
  argument :id, ID, required: true
end

def food(id:)
  Food.find(id)
end

In GraphiQL, the query then includes the argument:

query {
  food(id: 1) {
    name
    placeOfOrigin
  }
}

The server resolves the record and returns only the requested attributes.

Practice: Querying by place_of_origin

Create a root field called find_food that filters foods by their place_of_origin. A sample query and response are provided in a companion gist.

Wrapping Up

This tutorial covered the creation of a GraphQL object tied to an existing Rails model, along with root fields to list and retrieve records. The graphql_controller’s execute method handles every request and returns a JSON payload. Object fields expose an object’s attributes; root fields serve as the entry points shaping those responses.

Mutations—the queries that create, update, or delete data—are the next step. The finished code for this part is available on the part-2-solution branch of the repository.