README.md in hanami-api-0.0.0 vs README.md in hanami-api-0.1.0

- old
+ new

@@ -1,36 +1,379 @@ -# Hanami::Api +# Hanami::API -Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/hanami/api`. To experiment with that code, run `bin/console` for an interactive prompt. +Minimal, extremely fast, lightweight Ruby framework for HTTP APIs. -TODO: Delete this and the text above, and describe your gem - ## Installation -Add this line to your application's Gemfile: +Add this line to your application's `Gemfile`: ```ruby -gem 'hanami-api' +gem "hanami-api" ``` And then execute: - $ bundle +```shell +$ bundle install +``` Or install it yourself as: - $ gem install hanami-api +```shell +$ gem install hanami-api +``` +## Performance + +Benchmark against an app with 10,000 routes, hitting the 10,000th to measure the worst case scenario. +Based on [`jeremyevans/r10k`](https://github.com/jeremyevans/r10k), `Hanami::API` scores first for speed, and second for memory footprint. + +### Runtime + +Runtime to complete 20,000 requests (lower is better). + +| Framework | Seconds to complete | +|------------|---------------------| +| hanami-api | 0.11628299998119473 | +| watts | 0.23525599995628 | +| roda | 0.348202999914065 | +| syro | 0.355627000099048 | +| rack-app | 0.6226229998283088 | +| cuba | 1.2913489998318255 | +| rails | 17.04722599987872 | +| synfeld | 171.83788800006732 | +| sinatra | 197.47695700009353 | + +### Memory + +Memory footprint for 10,000 routes app (lower is better). + +| Framework | Bytes | +|------------|--------| +| roda | 47252 | +| hanami-api | 53988 | +| cuba | 55420 | +| syro | 60256 | +| rack-app | 82976 | +| watts | 84956 | +| sinatra | 124980 | +| rails | 143048 | +| synfeld | 172680 | + ## Usage -TODO: Write usage instructions here +Create `config.ru` at the root of your project: +```ruby +# frozen_string_literal: true + +require "bundler/setup" +require "hanami/api" + +class App < Hanami::API + get "/" do + "Hello, world" + end +end + +run App.new +``` + +Start the Rack server with `bundle exec rackup` + +### Routes + +A route is a combination of three elements: + + * HTTP method (e.g. `get`) + * Path (e.g. `"/"`) + * Endpoint (e.g. `MyEndpoint.new`) + +```ruby +get "/", to: MyEndpoint.new +``` + +### HTTP methods + +`Hanami::API` supports the following HTTP methods: + + * `get` + * `head` + * `post` + * `patch` + * `put` + * `options` + * `trace` + * `link` + * `unlink` + +### Endpoints + +`Hanami::API` supports two kind of endpoints: block and Rack. + +#### Rack endpoint + +The framework is compatible with Rack. Any Rack endpoint, can be passed to the route: + +```ruby +get "/", to: MyRackEndpoint.new +``` + +#### Block endpoint + +A block passed to the route definition is named a block endpoint. +The returning value will compose the Rack response. It can be: + +##### String + +```ruby +get "/" do + "Hello, world" +end +``` + +It will return `[200, {}, ["Hello, world"]]` + +##### Integer + +```ruby +get "/" do + 418 +end +``` + +It will return `[418, {}, ["I'm a teapot"]]` + +##### Integer, String + +```ruby +get "/" do + [401, "You shall not pass"] +end +``` + +It will return `[401, {}, ["You shall not pass"]]` + +##### Integer, Hash, String + +```ruby +get "/" do + [401, {"X-Custom-Header" => "foo"}, "You shall not pass"] +end +``` + +It will return `[401, {"X-Custom-Header" => "foo"}, ["You shall not pass"]]` + +### Block context + +When using the block syntax there is a rich API to use. + +#### env + +The `#env` method exposes the Rack environment for the current request + +#### status + +Get HTTP status + +```ruby +get "/" do + puts status + # => 200 +end +``` + +Set HTTP status + +```ruby +get "/" do + status(201) +end +``` + +#### headers + +Get HTTP response headers + +```ruby +get "/" do + puts headers + # => {} +end +``` + +Set HTTP status + +```ruby +get "/" do + headers["X-My-Header"] = "OK" +end +``` + +#### body + +Get HTTP response body + +```ruby +get "/" do + puts body + # => nil +end +``` + +Get HTTP response body + +```ruby +get "/" do + body "Hello, world" +end +``` + +#### params + +Access params for current request + +```ruby +get "/" do + id = params[:id] + # ... +end +``` + +#### halt + +Halts the flow of the block and immediately returns with the current HTTP status + +```ruby +get "/authenticate" do + halt(401) + + # this code will never be reached +end +``` + +It sets a Rack response: `[401, {}, ["Unauthorized"]]` + +```ruby +get "/authenticate" do + halt(401, "You shall not pass") + + # this code will never be reached +end +``` + +It sets a Rack response: `[401, {}, ["You shall not pass"]]` + +#### redirect + +Redirects request and immediately halts it + +```ruby +get "/legacy" do + redirect "/dashboard" + + # this code will never be reached +end +``` + +It sets a Rack response: `[301, {"Location" => "/new"}, ["Moved Permanently"]]` + +```ruby +get "/legacy" do + redirect "/dashboard", 302 + + # this code will never be reached +end +``` + +It sets a Rack response: `[302, {"Location" => "/new"}, ["Moved"]]` + +#### back + +Utility for redirect back using HTTP request header `HTTP_REFERER` + +```ruby +get "/authenticate" do + if authenticate(env) + redirect back + else + # ... + end +end +``` + +#### json + +Sets a JSON response for the given object + +```ruby +get "/user/:id" do + user = UserRepository.new.find(params[:id]) + json(user) +end +``` + +```ruby +get "/user/:id" do + user = UserRepository.new.find(params[:id]) + json(user, "application/vnd.api+json") +end +``` + +### Scope + +Prefixing routes is possible with routing scopes: + +```ruby +scope "api" do + scope "v1" do + get "/users", to: Actions::V1::Users::Index.new + end +end +``` + +It will generate a route with `"/api/v1/users"` as path. + +### Rack Middleware + +To mount a Rack middleware it's possible with `.use` + +```ruby +# frozen_string_literal: true + +require "bundler/setup" +require "hanami/api" + +class App < Hanami::API + use ElapsedTime + + scope "api" do + use ApiAuthentication + + scope "v1" do + use ApiV1Deprecation + end + + scope "v2" do + # ... + end + end +end +``` + +Middleware are inherited from top level scope. + +In the example above, `ElapsedTime` is used for each incoming request because +it's part of the top level scope. `ApiAuthentication` it's used for all the API +versions, because it's defined in the `"api"` scope. `ApiV1Deprecation` is used +only by the routes in `"v1"` scope, but not by `"v2"`. + ## Development After checking out the repo, run `bin/setup` to install dependencies. 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](https://rubygems.org). ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/jodosha/hanami-api. +Bug reports and pull requests are welcome on GitHub at https://github.com/hanami/api.