Library / SDK
json-api-dotnet/JsonApiDotNetCore avatar
json-api-dotnet/JsonApiDotNetCore

JsonApiDotNetCore: JSON:API for ASP.NET Core, and the OpenAPI gap it leaves

A framework for building JSON:API compliant REST APIs using ASP.NET and Entity Framework Core.

715 stars166 forksC#MIT

At a glance

What is it?
JsonApiDotNetCore turns an ASP.NET Core project into a JSON:API compliant API with filtering, sorting, pagination and side-loading handled for you. The trade-off is a contract you do not design, and an OpenAPI story that was discontinued rather than replaced.
Who is it for?
Adopt JsonApiDotNetCore when you already serve JSON:API on ASP.NET Core and Entity Framework Core and want query handling and relationship serialization removed from your controllers. Do not adopt it for a hand-tuned contract, for a handful of endpoints, or for an OpenAPI-first workflow, because the README states OpenAPI support is discontinued with nothing documented in its place.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The layer JsonApiDotNetCore takes off your hands

JsonApiDotNetCore is a framework for building JSON:API compliant REST APIs with ASP.NET Core and Entity Framework Core. The problem it addresses is narrow and worth naming: JSON:API is a strict format, and a format that strict pushes every request onto a predictable path. The library takes responsibility for the mechanics that format implies, including sorting, filtering, pagination, sparse fieldset selection, and side-loading related resources, so the developer is left defining resources and writing business logic. That positioning explains the audience. Teams already committed to the jsonapi.org format, already on ASP.NET Core, already persisting through Entity Framework Core, and tired of rebuilding the same query handling in every controller will find the value here. A team with an existing REST surface and a hand-tuned contract will find little to gain, because the library's promise is that the contract follows the format rather than following the application.

Attributes on the entity decide the shape of the API

Resources are described in the domain model rather than in controllers. The example in the README declares a Person class carrying three attributes: Resource marks the type as a resource, Attr marks a property as an attribute in the payload, and HasMany marks a collection as a relationship. The class inherits from Identifiable<long>, which is where the id comes from, and the type shows up as people in responses, derived from the class name. Relationships are declared as an ISet, so the framework can walk them without depending on navigation conventions. The practical consequence is that the API surface and the database model are the same object. Renaming a property renames the field clients read, and nothing warns you that a client depended on the old name. You can also see what the framework does not do: the documentation shows no way to publish a property that is neither an attribute nor a relationship, and no way to shape the id. Behaviour beyond the documented attributes is not described.

The JSON:API envelope the framework builds around your data

The response the framework produces is the JSON:API envelope, and the example request makes its parts visible. A GET request filters on equals(firstName,'John') and asks for include=children, and the answer arrives as a document with links, data, included and meta. Each record in data carries a type, an id, an attributes object, a relationships object, and its own links. Side-loaded children are not nested under the parent. They arrive in a separate included array and are referenced by type and id, while the relationships object points at /people/1/relationships/children and /people/1/children. Links are relative in this sample because the configuration sets UseRelativeLinks, and a total count appears in meta because the sample enables IncludeTotalCount. The description also states support for the Atomic Operations extension, which lets a client send several changes in a single request, though the getting started steps do not exercise it.

From an empty folder to a filtered and sideloaded response

The getting started steps walk through a first response in order. Start from a new ASP.NET Core Web API project created with controllers and with the OpenAPI scaffold turned off, then move into it:

bash
dotnet new webapi --no-openapi --use-controllers --name ExampleJsonApi
cd ExampleJsonApi

Next add the framework package and the Entity Framework Core provider of your choice. The sample uses SQLite:

bash
dotnet add package JsonApiDotNetCore
dotnet add package Microsoft.EntityFrameworkCore.Sqlite

Declare the entity with the resource attributes, and let the relationship collection be an ISet:

csharp
[Resource]
public class Person : Identifiable<long>
{
    [Attr] public string? FirstName { get; set; }
    [Attr] public string LastName { get; set; } = null!;
    [HasMany] public ISet<Person> Children { get; set; } = new HashSet<Person>();
}

Register the context and the framework in Program.cs, where UseRelativeLinks keeps links relative and IncludeTotalCount adds the count to meta:

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddJsonApi<AppDbContext>(options =>
{
    options.UseRelativeLinks = true;
    options.IncludeTotalCount = true;
});

var app = builder.Build();
app.UseRouting();
app.UseJsonApi();
app.MapControllers();
await CreateDatabaseAsync(app.Services);
app.Run();

The context in the sample points at a local SQLite file, and the seeding code drops and recreates the database on startup:

csharp
builder.UseSqlite("Data Source=SampleDb.db");

Start the API with dotnet run, then send the request that the README uses. You should get one John Doe with a child, the child in the included array, and a total of one:

http
GET http://localhost:5000/people?filter=equals(firstName,'John')&include=children HTTP/1.1

PostgreSQL and pgAdmin for running the test suite locally

Running the project's own tests needs a database, and the repository ships a compose file for it. The file at the repository root starts PostgreSQL in one container and pgAdmin in a second, and its comment states that the database is required for running tests locally. Startup is a detached compose up that waits for the containers, and teardown is a plain compose down, with a volumes flag that also resets the stored data:

bash
docker compose up --detach --wait
docker compose down
docker compose down --volumes

PostgreSQL is published on port 5432 and pgAdmin on 5050. The file states that opening http://localhost:5050 logs into pgAdmin automatically and connects to the database, and the credentials are set in the compose file itself, with postgres as both user and password. The image tags are latest rather than pinned, so a rebuild can pull a different database version than the one the project was last checked against. The repository root also holds a benchmarks directory, a nuget.config, and a src/Examples directory that the README describes as ready-to-run sample API projects.

The OpenAPI support that was dropped and never replaced

One feature was removed rather than deprecated. The README carries a note that OpenAPI support is discontinued, with a link to issue 2059, and the scaffold step in the getting started instructions asks for the no-OpenAPI switch, so a new project arrives without a Swagger document. Nothing in the documented setup replaces it. There is no generator, no attribute, and no configuration key for producing an OpenAPI schema. For teams whose contract lives in that schema, the work moves elsewhere, to hand-written documentation or a separate generator, and the repository does not absorb that cost. It also rules the library out as the single tool in an OpenAPI-first workflow, whatever its other merits, so a shop standardising on schema-driven client generation should settle that question before the first endpoint is written.

Hand-written controllers versus a framework, and the 5.x upgrade line

The alternative to this framework is not another library but a different approach: hand-written ASP.NET Core controllers over Entity Framework Core, with sorting, filtering and pagination coded as queryable extensions and relationships serialized by hand. That approach keeps the contract under your control and lets it drift toward what clients actually need, and it avoids the learning curve of attributes and the JSON:API format. The cost is the boilerplate the framework set out to remove, and it grows with every resource. The trade-off is clearest when the application has one or two endpoints, where the overhead exceeds the savings and you are bound to a format you did not choose. Maintenance facts are simple to state. The repository is not archived, the last push was on 2026-10-20, and the recent releases are v5.12.0 on 2026-10-09, v5.11.0 on 2026-05-22 and v5.10.0 on 2025-11-27, so work lands in the 5.x line. The project carries the MIT licence, which is permissive for commercial use. A VERSIONING_POLICY.md file sits at the repository root, and its contents are not reproduced in the README, so how a future major transition would be handled is worth reading before you commit to a long-lived API.

Editorial conclusion

Adopt JsonApiDotNetCore when you already serve JSON:API on ASP.NET Core and Entity Framework Core and want query handling and relationship serialization removed from your controllers. Do not adopt it for a hand-tuned contract, for a handful of endpoints, or for an OpenAPI-first workflow, because the README states OpenAPI support is discontinued with nothing documented in its place. Before committing, verify that your clients accept the JSON:API envelope and the type names derived from your resource classes, and read VERSIONING_POLICY.md at the repository root, since the 5.x release line means upgrades land inside a major version.

Frequently asked questions

What does JsonApiDotNetCore actually do?

It is a framework for building JSON:API compliant REST APIs with ASP.NET Core and Entity Framework Core. Sorting, filtering, pagination, sparse fieldset selection and side-loading of related resources are provided out of the box, while you define the resources and write the business logic.

How do I install JsonApiDotNetCore in a new ASP.NET Core project?

The getting started steps create a project with dotnet new webapi and then add the package with dotnet add package JsonApiDotNetCore, together with a provider such as Microsoft.EntityFrameworkCore.Sqlite. The package is published on NuGet under the name JsonApiDotNetCore.

Does JsonApiDotNetCore still support OpenAPI?

No. The README states that OpenAPI support is discontinued and points to issue 2059, and the scaffold command in the setup steps uses the no-OpenAPI switch so the new project has no Swagger document.

How do I run the JsonApiDotNetCore tests locally?

The repository ships a docker-compose.yml that starts PostgreSQL on port 5432 and pgAdmin on http://localhost:5050, both configured with the postgres credentials, and the file notes that the database is required for running the tests locally.

Official sources

  1. json-api-dotnet/JsonApiDotNetCore on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/json-api-dotnet-jsonapidotnetcore.svg)](https://hysenlabs.com/projects/json-api-dotnet-jsonapidotnetcore)

Was this useful?