There have been innumerable papers, posts and articles providing guidelines on how to design RESTful web services. Most of these guidelines are intended to make the behavior of web services more predictable for humans and machines alike. For example, the appropriate use of HTTP verbs allows developers to easily distinguish safe operations (such as GET and HEAD) from unsafe operations (such as POST and DELETE). Intermediate proxy servers can use this same information to determine candidates for caching. The plethora of HTTP headers provide many additional ways to express metadata and API structure in a standardized form.

While this slowly evolving consensus on "the right way" to design new APIs makes understanding such APIs easier, it does not necessarily help when actually consuming them. While HTTP libraries and tools expose all the underlying components (HTTP verbs/methods, headers, etc.) the burden of actually interpreting and combining all the pieces still lies with the developer. Let us take a look at an example:

Assume the URI /people/ represents a collection of address book entries. GETing the collection itself returns all entries. GETing an URI like /people/someid gives you a single entry. POSTing to the collection adds a new element to it and returns the URI of the newly created resource using the Location header.

Pretty standard stuff, right? The problem is that all this knowledge currently only exists in your head. When you actually perform operations on the collection in your code you are usually manually building your GETs and your POSTs while serializing and deserializing message bodies to and from JSON.

var client = new HttpClient {BaseAddress = new Uri("http://myservice/")};

var peopleResponse = await client.GetAsync("people");
var peopleList = await peopleResponse.Content.ReadAsAsync<List<Person>>();

var janeResponse = await client.GetAsync("people/jane");
var jane = await janeResponse.Content.ReadAsAsync<Person>();

await client.PostAsJsonAsync("people", new Person("John"));

This is where TypedRest comes in. TypedRest is a .NET and Java library for consuming RESTful APIs that behave in a "predictable" way. Rather than applying your knowledge about how a REST collection usually behaves you simply tell the library that this particular endpoint is a collection and get a collection-like interface in return.

var myService = new EntryEndpoint(new Uri("http://myservice/"));
var people = new CollectionEndpoint<Person>(myService, relativeUri: "people");

var peopleList = await people.ReadAllAsync();
var jane = await people["jane"].ReadAsync();
await people.CreateAsync(new Person("john"));

TypedRest uses a classic object-oriented approach to provide you with building blocks for modeling REST endpoints. Behavior of endpoints is described by inheritance while navigation between them is described by composition. For example, we could redesign our sample from above to make the service's functionality easy to discover and consume using code completion:

class MyServiceEndpoint : EntryEndpoint
  public MyServiceEndpoint(Uri uri) : base(uri)

  public ICollectionEndpoint<Person> People => new CollectionEndpoint<Person>(this, relativeUri: "people");

The consuming code could look this:

var myService = new MyServiceEndpoint(new Uri("http://myservice/"));
var peopleList = await myService.People.ReadAllAsync();
var jane = await myService.People["jane"].ReadAsync();
await myService.People.CreateAsync(new Person("john"));

TypedRest is all about nomenclature and patterns. An endpoint describes any resource addressable via an URI.

  • An entry endpoint represents the top-level URI of an API. It takes care of shared concerns such as authentication.
  • An element endpoint is a singular resource that can be read, modified and deleted.
  • A collection endpoint can list and add elements as well as provide element endpoints for individual elements.
  • A trigger endpoint represents an RPC-like call to trigger a single action.

There are also a number of more specialized endpoint types such as pagination-aware collections. Each of these endpoint types has one or more corresponding classes in TypedRest.

The only requirement on the server-side is that at least a part of the underlying pattern is implemented. For example, a collection endpoint class works as long as the server responds with an array of elements when GETting the collection while GETting a child endpoint provides a single element of the same type. If adding new elements via POST is not supported (yet) this will simply result in an exception at runtime when calling .CreateAsync(). For more graceful degradation TypedRest also exposes information about "allowed" methods as reported by OPTIONS.

We consider TypedRest's design to be opinionated yet pragmatic. The path of least resistance is to make your API match the patterns implemented in the built-in classes. These usually align with what is widely considered as "best practice". However:

  • We explicitly support some unRESTful concepts such as the RPC-like trigger endpoints mentioned above.
  • HATEOAS-style, link-based navigation is possible but entirely optional.
  • Link information is preferably encoded in HTTP headers instead of response bodies (although the latter is also supported in form of HAL).
  • TypedRest does not use custom MIME types for API versioning, navigation, etc..

Of course, we don't expect our predefined patterns to cover all possible use cases. This where good old "extension through inheritance" comes into play. Lets say our sample API from above also allows us to trigger a phone call to a person. We need to extend ElementEndpoint for individual Person instances to expose this functionality. We also need to replace CollectionEndpoint with something that builds instances of our specialized element endpoint rather than using ElementEndpoint. Let's get coding!

class MyServiceEndpoint : EntryEndpoint
  public MyServiceEndpoint(Uri uri) : base(uri)

  public PersonCollectionEndpoint People => new PersonCollectionEndpoint(this);

// CollectionEndpointBase<TEntity, TElementEndpoint> is the superclass of CollectionEndpoint<TEntity>.
// The TElementEndpoint type argument allows you to customize the specific type of element endpoint it creates.
class PersonCollectionEndpoint : CollectionEndpointBase<Person, PersonEndpoint>
  // Hard-coding the relative URI here makes the constructor signature nicer
  public PersonCollectionEndpoint(IEndpoint referrer) : base(referrer, relativeUri: "people")

  public override PersonEndpoint this[Uri relativeUri] => new PersonEndpoint(this, relativeUri);

class PersonEndpoint : ElementEndpoint<Person>
  public PersonEndpoint(IEndpoint referrer, Uri relativeUri) : base(referrer, relativeUri)

  // ActionEndpoint is a specific type of trigger endpoint that takes no input and provides no output
  public IActionEndpoint Call => new ActionEndpoint(this, relativeUri: "call");

The consuming code could look this:

var myService = new MyServiceEndpoint(new Uri("http://myservice/"));
await myService.People["jane"].Call.TriggerAsync();