Reason for celebration, after two years of development, there is another major release of JsonApiDotNetCore (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, 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:
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. 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:
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:
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.
rm WeatherForcast.cs
rm Controllers/WeatherForecastController.cs
And create a new controller in Controllers/PeopleController.cs
.
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:
// 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:
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:
{
"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
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.
Top comments (0)