[Jens Neuse](/content/people/jens-neuse/index.html)

CEO & Co-Founder at WunderGraph

March 14, 2026·5min read

Every API starts with a question. The question is usually: "What data can our service expose?"

But that's the wrong question.

The _right_ question is: "What is the consumer trying to achieve?"

This approach to schema design, starting from the consumer ( [consistent with GraphQL’s product-centric design philosophy](https://graphql.org/)), changes everything about how your API ends up looking.

## The Problem With Backend-Out API Design

Here's how most APIs get built.

A backend team has a service, and they expose the fields that already exist in their database model:

```graphql
type User @key(fields: "id") {
  id: ID!
  firstName: String!
  lastName: String!
  email: String!
  createdAt: DateTime!
  updatedAt: DateTime!
  role: UserRole!
  departmentId: ID!
  managerId: ID
  isActive: Boolean!
}
```

This is a perfectly reasonable schema. It maps to the database model and every field is accurate.

But it's designed from the backend out.

The field is called `departmentId` because that is the foreign key in the database. If a consumer wants the department _name_, they need to make a second query. `managerId` returns only an ID and the consumer has to resolve it themselves. `createdAt` and `updatedAt` are exposed because they exist, not because anyone asked for them.

Now multiply this across 50 subgraphs, where each team exposes what they have. The supergraph is a concatenation of database models. Technically correct. Practically painful.

## What Is a Dream Query?

A [dream query](https://hub-docs.wundergraph.com/concepts/dream-queries) is the query the consumer would write if there were no technical constraints.

Instead of asking "What can we expose?", ask the frontend team: "If you could query anything, what would the ideal query look like?"

For a user profile page, they could write:

```graphql
query UserProfile($id: ID!) {
  user(id: $id) {
    displayName
    email
    role
    department {
      name
      head {
        displayName
      }
    }
    manager {
      displayName
      email
    }
    recentActivity(limit: 5) {
      description
      timestamp
    }
  }
}
```

Notice what's different.

- `displayName` instead of `firstName` + `lastName` because the consumer doesn't care about the split
- `department` is an object rather than an ID, because the consumer wants the name
- `manager` is a resolved user rather than an ID reference
- `recentActivity` appears directly on the user, with no separate endpoint
- `createdAt` and `updatedAt` are absent because they aren't needed

This query is a _specification_. It tells you exactly what the API should look like from the perspective of someone who will use it.

## Working Backwards

Once you have the dream query, you work backwards.

**Step 1: Identify the gaps.**

Compare the dream query to your current supergraph. What exists? What's missing?

- `displayName` doesn't exist, so it needs to be added.
- `department` as an object, currently there is only `departmentId`.
- `recentActivity` doesn't exist on `User` at all.

**Step 2: Design the schema change.**

Add the missing types and fields to the supergraph:

**Step 3: Decompose into subgraph responsibilities.**

Now figure out _who_ builds what:

- `displayName` → Users team (simple computed field)
- `department` as object → Department team provides the entity, Users team extends with the relationship
- `manager` → Users team (already has `managerId`, just needs to resolve it)
- `recentActivity` → Activity tracking team, extends the `User` entity

This is where [Fission](/content/blog/graphql-federation-was-built-backwards/index.html) automates the mechanical work. The architect assigns new types and fields to subgraphs, and Fission handles the consequences. Hub routes the proposals to affected teams for review.

## Why Dream Queries Produce Better APIs

When you design from the backend out, the API reflects implementation details. Foreign keys can leak through, internal names persist, and the consumer has to understand how the backend works to use the API effectively.

When you design from the dream query, the API reflects consumer intent. Names are meaningful. Relationships are resolved.

Some concrete differences in schema design:

| Backend-first                     | Dream query-first                   |
|-----------------------------------|------------------------------------|
| `departmentId: ID!`              | `department: Department!`          |
| `firstName` + `lastName`         | `displayName`                      |
| `managerId: ID`                  | `manager: User`                    |
| Separate `/activity` endpoint     | `recentActivity` on `User`        |
| Fields exposed because they exist  | Fields exposed because they're needed|

The dream query approach also catches design problems early. If the dream query requires data from three different teams, you discover that _before_ implementation, not after.

## Who Should Write the Dream Query?

The dream query is a conversation starter, not a frontend-only tool.

**Product owners** can describe what a feature needs without knowing GraphQL syntax. "The profile page shows the user's name, their department, their manager, and recent activity." That's a dream query in plain English.

**Mobile developers** have different needs than web developers. Their dream queries might request less data or different fields optimized for smaller screens. Both queries inform the same supergraph design.

**AI agents** are the newest consumer. An agent's "dream query" is whatever data it needs to complete a task. Agents benefit most from consumer-first schema design because they can't compensate for bad naming or missing relationships the way a human developer can.

## A Practical Starting Point

You don't need special tooling to start using dream queries.

Next time a team requests a schema change, ask them: "What's your dream query? If you could query anything, what would it look like?"

Write it down. Compare it to your current supergraph. Note the gaps.

Even without automating the decomposition, this conversation changes the dynamic. It shifts schema design from "what can the backend expose?" to "what should the consumer experience?"

That shift in perspective is the most valuable part.

With [Hub](/content/hub/index.html), you can take this further. The visual canvas lets teams propose dream queries, see how they map to the existing graph, identify gaps, and create proposals — all in one place.

But the principle works regardless of tooling. For more on designing schemas around what clients need, see our [principles for designing good GraphQL schemas](/content/blog/graphql-schema-design-principles/index.html).

Start with the consumer. Design the query they'd want to write. Then figure out how to build it.

## Frequently Asked Questions (FAQ)

**What is a dream query?**  
A dream query is the GraphQL query a consumer would write if there were no technical constraints. It represents the ideal API from the consumer perspective and serves as the starting point for API design, rather than backend service structure.

**Who writes the dream query?**  
Anyone who consumes the API: frontend engineers, product owners, mobile developers, or even AI agents. The point is that API design starts with the consumer, not the backend team.

**Does this only work with GraphQL?**  
The concept applies to any API design, but GraphQL makes it practical because the query language naturally expresses consumer intent. With REST, there is no standard way to express "this is the data I want" independent of endpoint structure.
