Getting Started
So you’re new to Praxis, you’ve read some of the cool stuff that it can do and you’re ready to give it a try.
Great! Let’s get started by creating an API resource from scratch with documentation, and querying our running API server from the command line. We’ll be making a simple Post resource for a blog application, but before we do that, we need to set up our app.
Setting Up a Praxis App
Once Praxis is installed, the easiest way to get up and running is to use the Praxis app generator to create a sample app. In order to use the app generator, you’ll need to install the Praxis gem.
bundle install
to install the app’s gem dependencies:
You already have a basic Praxis application, that includes a sample resource called hello! Now you can look at the routes it exposes:
Use rackup to start the app:
Now you can hit one of the app’s routes to see it working:
Congratulations, you’ve just received a response from your first Praxis app! Try these other commands, and see what you get:
Once you’ve finished testing the Hello
controller, type CTRL+c
in the
terminal where you started the server earlier to kill your Praxis app.
Now it’s time to get your hands dirty and see how to use Praxis to build services with full-featured REST APIs.
Other Dependencies
Praxis uses the randexp library to generate examples given regular expressions, which expects to find a word list (dictionary) in one of three common locations: /usr/share/dict/words
or /usr/dict/words
, or ~/.dict/words
.
This should be present by default in OS X as well as most (but not all) Linux distributions. If you receive an error like “Words file not found. Check if it is installed…” then you need to install a package that provides one.
For apt-based distributions, you can install one with the wamerican
package (see the relevant debian or ubuntu pages for more information or alternatives).
For yum-based distributions, you need the the words
package.
Design vs Implementation
One of the goals of the Praxis framework is to allow designers to define a complete API specification without writing a single line of business logic. When creating a Praxis application, design and implementation should be treated as separate, independent phases.
The output of the design phase is a full specification of the API. This includes API versions, resources, actions, routes, parameter validation, and definitions of the media types returned by every action. You can view a human-readable version of the API specification using the API browser included with Praxis, but the specification is also available in JSON format.
Praxis differentiates itself from other frameworks in that everything defined in the design phase is actual code that will be enforced when the app runs. In other words, the API specification is intimately tied to its implementation. Praxis guarantees that link is never broken, so the documentation is always correct with respect to API behavior. A forgetful developer or documenter cannot cause the documentation to become stale.
The second phase of building an API consists of actually implementing the business logic behind each of the controller actions. However, this excludes all the boilerplate code for validating and coercing incoming parameters because the framework takes care of that based on the specification you’ve already defined in the specification.
Design Phase
To build a Praxis app, start by designing the API. This example API will expose
a Post
resource and two simple actions: show
and create
.
Creating a ResourceDefinition
To expose an API resource in Praxis, create its resource definition which is
just a Ruby class that includes Praxis::ResourceDefinition
:
Use the version
method to define which API version this resource supports.
Define show
and create
actions by calling the action
method.
Note: We wrapped the Posts
class here in the V1
and ResourceDefinitions
modules to better organize our code. This also allows us to create other
versions of the Posts resource later.
Now you have two actions defined, but Praxis needs you to fill in the interface specification before you can use them. Take the following example:
In the :show action definition, use the routing
DSL block to specify that
you want to respond to the GET
HTTP method for requests to the /posts/:id
URL path. For example, requests such as GET /posts/1
will be routed to
our show
action. Furthermore, the :id
attribute in the URL is
of type Integer
and is marked as required. The action also accepts an
optional :allow_deleted
parameter, which could allow clients to indicate that
they wish to retrieve posts even if they have been deleted. By making
:allow_deleted
an Attributor::Boolean
and setting its default value
to false
, you can avoid worrying about these details during
implementation and improve the clarity of the generated documentation.
Use the routing
DSL block again to mount the create
action to the
POST
HTTP verb /
path. This action has no required or optional URL
parameters, but it does accept a payload with a required title
attribute,
and an optional content
attribute. Both attributes are of type
String
.
Note: You might have noticed that the show
action’s routing block uses
get '/:id'
rather than get '/posts/:id'
. Very observant! This is
because every ResourceDefinition
class has a default prefix that applies
to all of its actions. The default prefix for a resource definition is the
snake-cased name of its class (e.g., Posts
becomes /posts
and
UserComments
becomes /user_comments
). You may specify an
alternative prefix by calling the prefix
method with the prefix you want
to use. See the documentation on Resource Definitions and
Actions for more information.
Displaying routes
Check the application’s routing table to make sure the Posts resource appears:
There they are! We can now see a show and a create action for the Posts resource. The observant reader might have also noticed that the implementation column tells you there are currently no implementations of these actions.
Building API documentation
Now you can see that Praxis knows about your Posts resource routes, but how can you start reviewing the interface details for these actions? Ask Praxis to generate documentation for you:
This generates a series of JSON files with the full specification of the API we have defined thus far. This JSON specification is readily consumable by dynamic client generators or converters to other existing formats.
Thankfully, Praxis can generate the same documentation in a more human-friendly format:
which should open up a new browser page for you, pointing to the documentation browser (typically at http://localhost:9090
). This task will automatically detect any changes to your design objects, and redisplay the results the browser for you. It is common to have the browser and the API design editor side by side, to see the changes as new things are added or edited.
If you have the praxis gem installed in the system, you can directly use its executable. In this case, typing praxis docs
would be equivalent to (bundle exec rake praxis:docs:preview
). See praxis --help
(and rake -T
) for more commands and information.
For our example, the doc browser should show you something like this, which incudes the already existing
definition of the sample ‘hello’ app, plus your Posts
resource, including
both actions and their URLs, parameters and payload specifications.
Creating a MediaType
An API specification should define not only what the service accepts, but also what it returns. One way to achieve this is to use application-specific internet media types in responses. And for extra credit we can also define the associated attribute structure of these media types.
In Praxis, media types:
- define their representations by declaring classes that extend
Praxis::MediaType
- can be associated with resource definitions as a default internet media type
- document the their representations and their use in resource definitions
So, how does that work for your Posts resource? Very easily.
Start by defining the representation of your Post
media type by creating a
class derived from Praxis::MediaType
. In that class, specify your internet
mediatype name (i.e. ‘application/vnd.acme.post’) using the identifier
DSL
method and proceed to define the media type’s attributes.
The create
action already describes two attributes for a Post
resource, but you can add more. In this example, you’ll add an id
and an
href
.
Once you have all the possible attributes listed, you can define one or more
named views using a view
DSL block. Each view may include a different
subset of attributes to display. Views are very useful when we wish to render
different attributes for different purposes. In this case, create a
default
view that contains all the attributes and a link
view
containing only the href of the post:
Save this as design/v1/media_types/post.rb and there you have it! A full Post media type. So now, how do you associate this Post media type with your Posts resource?
To use this newly defined media type, change the media_type
declaration
in our Posts resource definition from th 'application/json' string to the full
class. By doing this, Praxis will know that, by default, actions belonging to the
Posts
` resource definition are likely to generate responses of this
MediaType.
Resource’s default media types are used when response declarations
that can take a media_type
parameters are left unspecified. For example,
our response :ok
in the show action is equivalent to response :ok, media_type: MediaTypes::Post
.
Associating a default MediaType to a resource definition has another convenient effect when defining payload attributes. It can help simplify syntax. Here’s how:
It is good practice in RESTful APIs to be able to accept incoming resource
payloads that closely match resource responses receive from the same API. If
you get a result from a show
action, you should be able to easily modify
parts of it, and re-POST it to the API to save some changes. Because of this,
Praxis will assume that any payload definition of any action is closely related
to the default MediaType of the Resource. By doing that, the designer can
define attributes by name, without being required to specify the type and/or
options that might exist in the associated MediaType.
In other words: payloads you define can inherit any attribute definition of the same name that exists in the MediaType associated with the resource definition.
So, by adding the default mediatype in the Posts resource, your create
action payload can be simplified from:
to:
Now you have integrated a fully defined MediaType with your resource definition. If you regenerate the documentation and take a peek, you’ll now see its full definition in all its glory.
Please see media types, for more information.
At this point you have designed a Posts
resource with actions. You have
defined their routes, their parameters and an associated MediaType structure
with some renderable views, all without writing a single line of controller or
business logic code. Pretty groovy, huh?
Discussing the API interface at this stage, using the API browser, might save a lot of time and effort before proceeding with the implementation of the actual business logic. But now it’s time for implementation.
Implementation Phase
Praxis controllers are semantically very similar to any other “C” in most existing MVC frameworks. They implement the business logic for the actions of a resource and renderer a suitable response. Praxis controllers do differ from those of other web frameworks in that Praxis controllers:
- are plain Ruby Objects (easy testing!)
- receive typed and named method parameters in their signature (with the types you have specified in your action definition of your resource)
Creating a controller
To create your Posts controller, start with a Posts class which includes the
Praxis::Controller
module. Then use the implements
method to tell
Praxis that this class implements the Posts
resource definition.
Define a method for each your actions, show and create. The method signature
follows the named params
attributes of your actions. The show
action
has id
and allow_deleted
params
and no payload
. The
create
action has a payload
but no params
. The payload
contents are not mapped to the method signature.
Please see controllers, for more information.
Your parameters are name-checked by Ruby at runtime, plus Praxis will make sure their values conform to the type you’ve defined in your resource definition. If there is any error loading or coercing values from the incoming request, Praxis will return an appropriate validation error response telling you exactly what happened. Score!
Generating responses
Response generation is a broad topic, not covered in great detail here. In general, a controller needs to return an instance of Praxis::Response-derived class. There is one exception for strings. If you return a string from a controller, Praxis will create an instance of the action’s default response and fill in the body with the string you returned.
There is much more related to responses that the framework provides, including defining their expectations, specifying which actions can return which resources, and runtime validation that returned responses match the specified expectations.
Please see responses, for more information.
Generating examples
Example generation is another area that requires much more explanation.
However, for the sake of completeness, and because of the suspicious
MediaTypes::Post.example.render(:default)
call to generate a hash in the
show
action), we should say a few words.
Praxis (in conjunction with its Praxis::Mapper and Attributor gem dependencies) comes equipped with a very powerful way to:
- generate unique (or repeatable) examples of MediaType objects
- render them using any of their defined views
Rendering refers to generating a hash representation of a MediaType’s attributes. That hash can then easily be taken and output to the specific desired format such as a JSON string.
In particular invoking a MediaType’s .example
class method will generate
an instance object with a fully-typed example structure as defined by its
attribute names and types. As a MediaType instance, you can then take that
object as ask it to be rendered according to one of the named views that you
have defined.
In this case, you have used the default
view, which should produce a hash
with values for id
, title
and content
keys. Being able to
generate examples for all kinds of types makes for nice documentation, but it
also can create real-looking API responses for your tests.
Conclusion
We now have a fully defined, and fully implemented Post API service. We can review its docs, check the routes, start it up, and test that it responds correctly in both success and error scenarios.
Start it up, and check it out!