Skip to main content

Endpoints

With Praxis, you can define all of the API endpoints that your API provides. An endpoint is commonly a set of API actions that are related to a given resource type or related set of actions. i.e., the Users endpoint might define all the actions to list, create and update users.

Defining an endpoint is done by creating a class that derives from Praxis::EndpointDefinition. Using this class you will be able to define all of the aspects of your endpoint including versioning, description, all actions, with their parameters, routing, responses, authentication and etc.

Here's a simple example of a Blogs endpoint definition which provides actions related to the MediaTypes::Blog media type.

class Blogs
include Praxis::EndpointDefinition

media_type MediaTypes::Blog
version '1.0'

description <<-EOS
Blogs is the Resource where you write your thoughts.

And there's much more I could say about this resource...
EOS

action :index do
routing { get '' }
description 'Fetch all blog entries'
response :ok, Praxis::Collection.of(MediaTypes::Blog)
end

action :show do
routing { get '/:id' }
description 'Fetch a single blog by id'
params do
attribute :id
end
response :ok
end

action :create do
description 'Create a new Blog'
routing { post '' }
payload reference: MediaTypes::Blog do
attribute :title
attribute :description
end
response :created
response :bad_request
end
end

At a glance, we have defined that this endpoint will respond only to Api version "1.0", it has a description, uses a routing prefix of "/blogs", and exposes simple :index, :show and :create actions. The :index action returns a collection of blogs and does not take any parameters. The :show action takes an id parameter and returns a single Blog media-type. While :index and :show respond to HTTP GET verbs, the :create action responds is accessible through a POST /blogs, and can take a payload that contains a title and a description field. Successful requests will return a 201 created HTTP code, while sending a bad parameters will cause a 400 Bad Request response with the body containing the cause.

Let's dig into the different configuration pieces available when defining endpoints

Description

You can specify a description for the endpoint definition using the description method. This description string is just for human consumption and is simply inserted directly into the generated API documentation.

class Blogs
include Praxis::EndpointDefinition
description <<-EOS
Blogs is the Resource where you write your thoughts.

And there's much more I could say about this resource...
EOS
end

Routing Prefix

Each endpoint definition has a routing prefix (partial path) which Praxis will automatically prepend to all of the routes found in all of the actions of its resource. By default, this prefix is the class name of the endpoint definition converted to snake-case. For our Blogs endpoint definition above, the default routing prefix is blogs. To override the default routing prefix, simply provide a value using the prefix method:

class Blogs
include Praxis::EndpointDefinition

prefix '/my-blogs'
end

Media Type

Since endpoints usually group a collection of actions that are related to a type, you can set the "default" media type of a endpoint definition. This way, if the majority of actions return the same media-type, you don't need to repeat it as much.

class Blogs
include Praxis::EndpointDefinition

media_type BlogMediaType
end

A MediaType in Praxis is often more than just an internet media-type string. It commonly refers to the structure or schema with which a given resource type will be displayed. This structure is also often associated with an internet media-type string (i.e. the string is the name for the structure schema).

The value you pass to the media_type method must be a:

  • Praxis::MediaType-derived class that defines the attributes available for representing an API resource.
  • string representing an internet media type identifier (i.e. 'application/json').

For more information on Praxis media types, please see MediaTypes.

Version

You can apply an API version to a endpoint definition by using the version method:

class Blogs
include Praxis::EndpointDefinition

version '1.0'
end

Setting the version of a endpoint definition allows you to have version control over the resources available through the API.

You can use any string as a version. By using the version method, you're telling Praxis to only dispatch actions for this resource when the incoming request carries the correct version value.

An incoming request can specify a version in three different ways:

  • By providing an X-Api-Version header containing the defined version string. Example: X-Api-Version: 1.0
  • By providing an :api_version parameter in the query containing the defined version string. Example: /blogs?api_version
  • By using an appropriate URL prefix. Example: /v1.0/blogs

You can read more about how to configure the allowable ways to version the API in the API definition section

Parent Resource

Often times, we want to expose certain API resources embedded into others. For this reason, Praxis provides an easy way to define the parent of a resource by using the parent directive. This will configure the resource to use its parent's canonical_path as the base for all of its actions (in addition to any prefix you may define on this resource). By default, a canonical path is the :show action of an endpoint (but can be changed using the canonical_path :<action_name> method in the definition)

Additionally, any parameters in the parent's route will also be applied as defaults in the child. The last route parameter is assumed to be an 'id'-type parameter, and is prefixed with the parent's snake-cased singular name. I.e., id from a Blog parent will be renamed to blog_id. Any other parameters are copied unchanged.

This behavior can be overridden by providing a mapping hash of the form { "parent_name" => "child_name" } to the parent directive.

For example, to define a Posts subresource of the above Blogs resource:

class Posts
include Praxis::EndpointDefinition

parent Blogs

action :show do
routing { get '/:id' }
end

end

This would result in the :show action responding to the following path /blogs/:blog_id/posts/:id, due to the canonical path of the Blogs resource being /blogs/:id.

To achieve a custom parent parameter we can change parent Blogs to parent Blogs, :id => :parent_id. This results in the following path: /blogs/:parent_id/posts/:id.

Action Defaults

There are often situations where many actions within a endpoint will require a common subset of definitions. For example, a common set of URL parameters, a common set of headers, traits or even a common set of allowed responses.

Praxis allows you to easily define and share common pieces of code across all actions by placing their definitions inside an action_defaults block at the endpoint definition level. Here is an example:

class Blogs
include Praxis::EndpointDefinition

action_defaults do
params do
attribute :dry_run, Attributor::Boolean, default: false
end
response :bad_request
end

action :index do
routing { get '' }
end

action :show do
routing { get '/:id' }
params do
attribute :id, String
end
end

end

The example above will cause the :dry_run parameter to be propagated and defined in all available actions of the Blogs endpoint definition (i.e., both :index and :show actions will have such a parameter).

With action_defaults you can use params, payload, headers, and response stanzas. If any of those stanzas are defined within an action itself Praxis will appropriately merge them. Therefore, in this example, the :show action will end up with both the :dry_run and :id parameters.

In case of conflict while merging, Praxis will always give overriding preference to definitions found within the action block itself.

Canonical Paths

By default the canonical path for an endpoint will point to the routing path of the :show action. However, you can specify which action should be used for the resource's canonical href with the canonical_path method:

class Blogs
include Praxis::EndpointDefinition

canonical_path :show
end

Having the appropriate canonical_path allows you to both generate and parse hrefs for a given resource by using:

  • EndpointDefinition.to_href(<named arguments hash>) to generate an href for the resource.
  • EndpointDefinition.parse_href(<href String>) to get a type-coerced hash of the parameters for the canonical action from the given string.

Given a controller (class or instance), you can use use those helpers by first calling its definition method to retrieve the EndpointDefinition it implements, and then using either to_href or parse_href as described above. For example:

class Posts
include Praxis::Controller

implements Endpoints::Posts

def show(id:)
Posts.definition.to_href(id: 1) # => /posts/1
Posts.definition.parse_href('/posts/1') # => { id: 1 }
# ...
end
end

nodoc!

TODO: DEPRECATE IT?? You can mark a resource for exclusion from generated documentation by using the nodoc! method:

{% highlight ruby %} class Blogs include Praxis::EndpointDefinition

nodoc! end {% endhighlight %}

Additionally, the resource's actions and media type (if specified) will not be used when determining which media types should be documented.

The next big thing to expore is how we can define each of the specific actions for the endpoint.