Relationships with JsonApiDotNetCore
Tags: csharp, jsonapi, api, rest
Published: 2020-12-21 09:54:15

In our previous post we setup a basic JSON:API compliant API with the 4.0 release of the [JsonApiDotNetCore](https://github.com/json-api-dotnet/JsonApiDotNetCore/releases/tag/v4.0.0) framework. You can find the code we wrote under the [`part-1`](https://github.com/wunki/json-api-example/tree/part-1) branch on Github.

In this post I would like to introduce some of the more powerful capabilities of the framework and to do so, let's talk about relationships.

## Relationships

We are going to use the canonical todo example and create the ability to assign a todo to a single person. Lets start by creating a new `TodoItem.cs` file and add the following to it:

```csharp
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;

namespace MyApi
{
    public class TodoItem : Identifiable
    {
        [Attr]
        public string Todo { get; set; }

        [Attr]
        public uint Priority { get; set; }
				
        [HasOne]
        public Person Owner { get; set; }
    }
}
```

In the code above, we state that a `TodoItem` has a single `Person` as its owner. Let's also add the reverse relationship, where we state that a `Person` can have many todo's. Open up your `Person.cs` and add the following:

```csharp
using System.Collections.Generic;
using JsonApiDotNetCore.Resources;
using JsonApiDotNetCore.Resources.Annotations;

namespace MyApi
{
    public class Person : Identifiable
    {
        [Attr]
        public string Name { get; set; }
        
        [HasMany]
        public ICollection<TodoItem> TodoItems { get; set; }
    }
}
```

We're also going to add our new `TodoItem` to our `DbContext` to release the power of Entity Framework Core:

```csharp
using Microsoft.EntityFrameworkCore;

namespace MyApi
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options) { }

        public DbSet<Person> People { get; set; }
        public DbSet<TodoItem> TodoItems { get; set; }
    }
}
```

To expose the new endpoint, let's create a controller, `TodoItemsController.cs` with the following boilerplate:

```csharp
using JsonApiDotNetCore.Configuration;
using JsonApiDotNetCore.Controllers;
using JsonApiDotNetCore.Services;
using Microsoft.Extensions.Logging;

namespace MyApi.Controllers
{
    public class TodoItemsController : JsonApiController<TodoItem>
    {
        public TodoItemsController(
                IJsonApiOptions options,
                ILoggerFactory loggerFactory,
                IResourceService<TodoItem> resourceService)
            : base(options, loggerFactory, resourceService)
        { }
    }
}
```

Before we run our API, make sure to delete the `my-api.db` file in our project folder for it to pick up the new schema.

We can now run our API and go to https://localhost:5001/todoItems and we will see the following:

```json
{
  "links": {
    "self": "https://localhost:5001/todoItems",
    "first": "https://localhost:5001/todoItems"
  },
  "data": []
}
```

It works, but it's a bit boring. Let's add some more data in our `Startup.cs` file:

```csharp
if (!context.People.Any())
{
    context.People.Add(new Person
    {
        Name = "John Doe",
        TodoItems = new List<TodoItem>
        {
            new TodoItem { Todo = "Make pizza", Priority = 1},
            new TodoItem { Todo = "Clean room", Priority = 2}
        }
     });
     context.SaveChanges();
}
```

Another query will show us the following, if we go to `/people` we will see:

```json
{
  "links": {
    "self": "https://localhost:5001/people",
    "first": "https://localhost:5001/people"
  },
  "data": [
    {
      "type": "people",
      "id": "1",
      "attributes": {
        "name": "John Doe"
      },
      "relationships": {
        "todoItems": {
          "links": {
            "self": "https://localhost:5001/people/1/relationships/todoItems",
            "related": "https://localhost:5001/people/1/todoItems"
          }
        }
      },
      "links": {
        "self": "https://localhost:5001/people/1"
      }
    }
  ]
}
```

And if we query `/todoItems` we see:

```json
{
  "links": {
    "self": "https://localhost:5001/todoItems",
    "first": "https://localhost:5001/todoItems"
  },
  "data": [
    {
      "type": "todoItems",
      "id": "1",
      "attributes": {
        "todo": "Make pizza",
        "priority": 1
      },
      "relationships": {
        "owner": {
          "links": {
            "self": "https://localhost:5001/todoItems/1/relationships/owner",
            "related": "https://localhost:5001/todoItems/1/owner"
          }
        }
      },
      "links": {
        "self": "https://localhost:5001/todoItems/1"
      }
    },
    {
      "type": "todoItems",
      "id": "2",
      "attributes": {
        "todo": "Clean room",
        "priority": 2
      },
      "relationships": {
        "owner": {
          "links": {
            "self": "https://localhost:5001/todoItems/2/relationships/owner",
            "related": "https://localhost:5001/todoItems/2/owner"
          }
        }
      },
      "links": {
        "self": "https://localhost:5001/todoItems/2"
      }
    }
  ]
}
```

Ok, if you thought this was cool, wait till you see the querying and filtering capabilities.

## Including Relationships

To get all todo-items for a single person, the JSON:API spec defines a relationships endpoint, which would be `/people/1/relationships/todoItems`:

```json
{
  "links": {
    "self": "https://localhost:5001/people/1/relationships/todoItems",
    "related": "https://localhost:5001/people/1/todoItems",
    "first": "https://localhost:5001/people/1/relationships/todoItems"
  },
  "data": [
    {
      "type": "todoItems",
      "id": "1"
    }
  ]
}
```

This shows you all the todo-items IDs for that specific person. To get all individual todo's would cost many queries.

Amazingly, JSON:API also enables you to get all that data in a single call, by doing:

`GET /people/1?include=todoItems`

```json
{
  "links": {
    "self": "https://localhost:5001/people/1?include=todoItems"
  },
  "data": {
    "type": "people",
    "id": "1",
    "attributes": {
      "name": "John Doe"
    },
    "relationships": {
      "todoItems": {
        "links": {
          "self": "https://localhost:5001/people/1/relationships/todoItems",
          "related": "https://localhost:5001/people/1/todoItems"
        },
        "data": [
          {
            "type": "todoItems",
            "id": "1"
          },
          {
            "type": "todoItems",
            "id": "2"
          }
        ]
      }
    },
    "links": {
      "self": "https://localhost:5001/people/1"
    }
  },
  "included": [
    {
      "type": "todoItems",
      "id": "1",
      "attributes": {
        "todo": "Make pizza",
        "priority": 1
      },
      "relationships": {
        "owner": {
          "links": {
            "self": "https://localhost:5001/todoItems/1/relationships/owner",
            "related": "https://localhost:5001/todoItems/1/owner"
          }
        }
      },
      "links": {
        "self": "https://localhost:5001/todoItems/1"
      }
    },
    {
      "type": "todoItems",
      "id": "2",
      "attributes": {
        "todo": "Clean room",
        "priority": 2
      },
      "relationships": {
        "owner": {
          "links": {
            "self": "https://localhost:5001/todoItems/2/relationships/owner",
            "related": "https://localhost:5001/todoItems/2/owner"
          }
        }
      },
      "links": {
        "self": "https://localhost:5001/todoItems/2"
      }
    }
  ]
}
```

OK, that was easy. But what if that's just too big of a payload and you do not want to waste any bandwidth? That's where "sparse fieldsets" come in.

You just want to show a list of the todo's? Let's reduce the payload:

`GET /people/1?include=todoItems&fields[todoItems]=todo`

```json
{
  ...
  "included": [
    {
      "type": "todoItems",
      "id": "1",
      "attributes": {
        "todo": "Make pizza"
      },
      "links": {
        "self": "https://localhost:5001/todoItems/1"
      }
    },
    {
      "type": "todoItems",
      "id": "2",
      "attributes": {
        "todo": "Clean room"
      },
      "links": {
        "self": "https://localhost:5001/todoItems/2"
      }
    }
  ]
}
```

Pretty powerful stuff! But there is even more. In the next post, I want to show you some of the powerful filter capabilities that are also added in the 4.0 release. 

Ow, and BTW, you can find all the code above in the [`part-2`] (https://github.com/wunki/json-api-example/tree/part-2) branch on Github.