Flon

Flon, the API maker that uses JSON. It focuses on not doing any magic: it’s just a thin and convenient wrapper over Rack, with a hopefully obvious DSL.

Installation

Add this line to your application's Gemfile:

gem 'flon'

And then execute:

bundle

Or install it yourself as:

gem install flon

Usage

Basic usage

To use Flon, simply make a plain class extending Flon::DSL. This will put the router DSL in your class scope.

require 'flon'

class MyAPI
  extend Flon::DSL

  def initialize
    @names = []
  end

  namespace '/names' do
    get '/'
    attr_reader :names

    post '/new'
    def add_name(request)
      @names << request.body
    end

    namespace '/:index' do
      get '/'
      def name_by_index(request)
        @names[request.params[:index].to_i]
      end

      put '/edit'
      def change_name(request)
        @names[request.params[:index].to_i] = request.body
      end
    end
  end
end

You can then create a Flon::API using this:

api = Flon::API.new(MyAPI.new)

Note how you initialise everything normally—you can use this for dependency injection without a headache.

Flon::API is a bog-standard Rack app, so you can just use it in config.ru, or whatever method you use.

Routing

The DSL defines methods for routing based on every HTTP request method, except HEAD, TRACE, OPTIONS, and CONNECT. HEAD is handled by GET routes but the body is discarded.

When you call a routing method, the next method you define will be bound to that route.

get '/route'
def get_route
  # matches GET /route and HEAD /route
end

post '/route'
def post_route
  # matches POST /route
end

put '/route'
def put_route
  # matches PUT /route
end

delete '/route'
def delete_route
  # matches DELETE /route
end

patch '/route'
def patch_route
  # matches PATCH /route
end

Each routing DSL method takes a single route pattern as a String. The route pattern is the exact same as Sinatra’s.

The bound method is called when an HTTP request matches that route with that HTTP method.

The method will receive one argument, the Flon::Request object describing the HTTP request:

request.body         # Gets the body; returns a String, or nil if no body

request.headers      # Gets the headers; returns a Hash with String keys and
                     # String values

request.method       # Gets the HTTP method; returns a Symbol with the name of
                     # the method lowercased

request.params       # Gets the route and query parameters, merged, giving
                     # priority to route parameters; returns a Hash with Symbol
                     # keys

request.path         # Gets the path; returns a String

request.query_params # Gets the query parameters, specifically; returns a Hash
                     # with Symbol keys

request.route_params # Gets the route parameters, specifically; returns a Hash
                     # with Symbol keys

request.url          # Gets the request’s URL; returns an URI object

Note that the argument will not be passed if the method does not accept at least one argument, so you can leave it out if you’re not gonna use it.

Namespaces

You can also namespace routes:

namespace '/users' do
  get '/'
  attr_reader :users # GET /users

  post '/new'
  def add_user(request) # /users/new
    @users << request.body
  end

  namespace '/:id' do
    get
    def user_by_id(request) # GET /users/:id
      @users[request.params[:id].to_i]
    end

    put '/edit'
    def edit_user(request) # PUT /users/:id/edit
      @users[request.params[:id].to_i] = request.body
    end
  end
end

Namespaces are entirely “virtual”, that is, they just concatenate their route patterns with their children, so they’re just there for DRYness.

Versioning

The version method applies a namespace throughout every route:

version '/v1'

get '/yes'
def yes
  'yes'
end

get '/no'
def no
  'no'
end

Going to /yes will give you a 404, but going to /v1/yes will give you "yes". The same thing happens with the /no route.

In reality, version is just an alias for namespace.

Responding

Whatever is returned by the route’s method is #to_json’d and sent back. If you want to return a custom response, return a Flon::Response object, which takes the status code as the first argument, the body as the second argument, and the headers as the third argument of its constructor:

get '/admin'
def admin
  Flon::Response.new(403, 'No. Go away.', 'X-Loser' => 'Yes')
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake to run rubocop and the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/unleashy/flon.

License

The gem is available as open source under the terms of the MIT License.