Skip to content

Relationships

In a relational database you express links with foreign keys and join tables. In GraphQL you express the same links far more directly: a relationship is simply a field whose type is another type. There is no special syntax — the type system you already know is all you need. What changes from one relationship to the next is only the shape of the field: a single value, a list, or a list on both sides.

The simplest link returns a single object. A Post belongs to exactly one Author, so the field’s type is just Author:

type Post {
id: ID!
title: String!
author: Author!
}

The reverse direction is one-to-many: one author has many posts. The field type becomes a list, [Post!]!:

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

That is the entire pattern. A “to-one” side returns a type; a “to-many” side returns a list of that type. The same two types can expose the relationship from both ends, letting a client walk in either direction.

When both sides can have many of the other, you have a many-to-many relationship — say a Post carries several Tags, and each Tag is attached to many posts. In SDL this is just a list field on each type:

type Post {
id: ID!
title: String!
tags: [Tag!]!
}
type Tag {
id: ID!
label: String!
posts: [Post!]!
}

There is no visible “join table” in the schema — that bookkeeping lives in your data layer. The client only sees two types, each with a list field pointing at the other.

flowchart LR
  Author["type Author"]
  Post["type Post"]
  Tag["type Tag"]
  Author -->|"posts: [Post!]! (one-to-many)"| Post
  Post -->|"author: Author!"| Author
  Post -->|"tags: [Tag!]! (many-to-many)"| Tag
  Tag -->|"posts: [Post!]!"| Post
One-to-many between Author and Post; many-to-many between Post and Tag.

Declaring the field is half the job; the other half is the resolver that fetches the related data when a client actually selects that field. A resolver for a relationship field receives the parent object and uses its id to look up what is connected. Because GraphQL only runs a resolver when its field is requested, a client that asks for name but not posts never pays the cost of loading posts at all.

The schema below wires up a one-to-many (Authorposts) and a many-to-many (PostTag), with resolvers that look up the other side by id. The query crosses three relationships in one request. Press Run.

JavaScript

Trace the output: from one author we resolved the one-to-many posts, and inside each post we resolved the many-to-many tags. Each relationship field had its own resolver that looked up the connected records by id. Notice we never wrote a SQL JOIN — the graph is the join, expressed as nested field selections.

How is a one-to-many relationship, like an author with many posts, expressed in SDL?
How does a many-to-many relationship between Post and Tag appear in the schema?
What runs to fetch the data on the other side of a relationship field?