Unleash the power of REST with JsonApiDotNetCore
Tags: csharp, jsonapi, api, rest
Published: 2020-12-21 09:50:07

Reason for celebration, after two years of development, there is another major release of [JsonApiDotNetCore](https://github.com/json-api-dotnet/JsonApiDotNetCore/releases/tag/v4.0.0) (JADNC), the 4.0 release. 

This release packs a lot of powerful features backed by 1000+ tests! To show off the amazing work by the team, I decided to write a three-part blog series where I'll demo its ease of use and  capabilities by building a new API.

But before we start, let me convince you why you should still use JSON:API and don't need to go to GraphQL for that powerful API.

## Levels

REST APIs come in levels and as developers like to do, it starts counting at 0.

Level 0 means it's using HTTP as the transport layer for remote communications. That's it.

**Level 1** is where we add resources. Instead of talking to a single endpoint, let's say `/api`, you get multiple endpoints, `/pizzas` and `/pastas`.

**Level 2** is when we add verbs. Communicate your intent on the resource by using HTTP verbs. Use GET when you know it is a read operation — does not make changes on the server — and POST when you want to change state.

In the final level, **Level 3**, the server adds URIs to the response which tell the client where to go if they want more information.

For some time the software industry has been releasing level 3 APIs and created specifications which guide you on the best practices. The most popular specification is the [JSON:API specification](https://jsonapi.org/), which you could guess, is used by us.

I also need to address another giant in this space. In 2015, Facebook released GraphQL to solve problems they experienced with the shift to mobile. GraphQL reduced the amount of network bandwith you need by its query capabilities. You can combine multiple requests into one, and get back the exact payload you need.

The downside of GraphQL was that it stepped away from some of the best practices on the web 👎.

You don't get Level 1 capabilities, where we have multiple endpoints, or Level 2, where we use HTTP verbs to communicate your intent.

Although GraphQL can use HTTP as a transport, it always uses a single endpoint. This means that decades of hardware and software and their caching solutions on top of HTTP standards can't cache GraphQL queries easily.

However! The communities' embrace of GraphQL showed us that people are happy to make these sacrifices for the increase in power they gained as consumers of APIs. Being able to reduce the amount of requests by introducing powerful query capabilities is an amazing thing to have.

In part 2 and 3 of this series we will show you how JSON:API gives you those capabilities, without loosing some of the benefits of REST. Before we do that, let's first cover the basics and get a project up and running.

## Getting started with JSON:API

Let's start with creating a web project and installing the JsonApiDotNetCore library:

```bash
dotnet new webapi -o MyApi
cd MyApi
dotnet add package JsonApiDotnetCore
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
```

You can run the project with `dotnet run` and a webserver will spin up and be available at [https://localhost:5000](https://localhost:5000/). It won't serve anything useful, for that we need to add models.

Let's add our first model by creating a `Person.cs` file and adding a Person:

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

namespace MyApi
{
    public class Person : Identifiable
    {
        [Attr]
        public string Name { get; set; }
    }
}
```

There are two important things to note here. First we make Person implement the `IIdentifiable<TId>` interface by inheriting from `Identifiable`. This assumes our model has a unique key called `Id` with integer values.

Next step is adding Entity Framework Core to the mix.

## Entity Framework Core

Entity Framework Core is a powerful ORM developed by Microsoft and JsonApiDotNetCore makes good use of it. To add the database as a storage mechanism for our model we need to create a new `AppDbContext.cs` file with the following:

```csharp
using Microsoft.EntityFrameworkCore;

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

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

The `DbSet` is used to be able to query and save the `Person` model to the database.

Finally, we need to wire up the endpoint by creating a controller. When we ran `dotnet new webapi -o MyApi`, it created a controller and a model for us. Let's delete those two files as we won't need them.

```bash
rm WeatherForcast.cs
rm Controllers/WeatherForecastController.cs
```

And create a new controller in `Controllers/PeopleController.cs`.

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

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

In the above code we see that the method receives `IJsonApiOptions`. It provides global options you can set in your application startup and lets you configure things like JSON casing and if clients are allowed to generate their own IDs.

Second we see the `IResourceService`, which hooks up the API to the persistence layer of the model.

To tie it all together we can enable JsonApiDotNetCore during startup of the project. Go to your `Startup.cs` file and replace `ConfigureServices` with the following:

```csharp
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add the Entity Framework Core DbContext like you normally would
    services.AddDbContext<AppDbContext>(options =>
    {
        // Use whatever provider you want, this is just an example
        options.UseSqlite("Data Source=my-api.db");
    });

    // Add JsonApiDotNetCore
    services.AddJsonApi<AppDbContext>();
}
```

This uses the `AppDbContext` we created earlier and uses SQLite as our database. Entity Framework Core supports MS SQL Server, SQLite, PostgreSQL, MySQL and Cosmos DB out of the box.

Finally, let's replace the default `Configure` method as well in our `Startup.cs` file with the following:

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, AppDbContext context)
{
    context.Database.EnsureCreated();

    if (!context.People.Any())
    {
        context.People.Add(new Person
        {
             Name = "John Doe"
        });
        context.SaveChanges();
     }

     app.UseHttpsRedirection();
     app.UseRouting();
     app.UseJsonApi();
     app.UseEndpoints(endpoints => endpoints.MapControllers());
}
```

The `UseJsonApi` installs middleware for handling the incoming requests.

This is everything that is required to have a JSON:API, let's give it a run with `dotnet run` and send a request:

`curl -k [https://localhost:5001/people](https://localhost:5001/people) | jq`

This returns a `200 OK` with a beautiful payload:

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

## What's next

This is all that's needed to get a JSON:API up and running. You can find all the code of this part on [Github under the `part-1`](https://github.com/wunki/json-api-example/tree/part-1) branch.

In our next post I'll explore the power of relationships and how JSON API can reduce the amount of requests we need to make to get the data we need.