README.md in hanami-api-0.2.0 vs README.md in hanami-api-0.3.0
- old
+ new
@@ -1,9 +1,55 @@
# Hanami::API
Minimal, extremely fast, lightweight Ruby framework for HTTP APIs.
-* [Installation](#installation)
+
+## Version
+
+**This branch contains the code for `hanami-api` 0.3.x.**
+
+## Status
+
+[![Gem Version](https://badge.fury.io/rb/hanami-api.svg)](https://badge.fury.io/rb/hanami-api)
+[![CI](https://github.com/hanami/api/workflows/ci/badge.svg?branch=main)](https://github.com/hanami/api/actions?query=workflow%3Aci+branch%3Amain)
+[![Test Coverage](https://codecov.io/gh/hanami/api/branch/main/graph/badge.svg)](https://codecov.io/gh/hanami/api)
+[![Depfu](https://badges.depfu.com/badges/a8545fb67cf32a2c75b6227bc0821027/overview.svg)](https://depfu.com/github/hanami/api?project=Bundler)
+[![Inline Docs](http://inch-ci.org/github/hanami/api.svg)](http://inch-ci.org/github/hanami/api)
+
+## Contact
+
+* Home page: http://hanamirb.org
+* Mailing List: http://hanamirb.org/mailing-list
+* API Doc: http://rdoc.info/gems/hanami-api
+* Bugs/Issues: https://github.com/hanami/api/issues
+* Support: http://stackoverflow.com/questions/tagged/hanami
+* Chat: http://chat.hanamirb.org
+
+## Rubies
+
+__Hanami::API__ supports Ruby (MRI) 3.0+
+
+## Installation
+
+Add these lines to your application's `Gemfile`:
+
+```ruby
+gem "hanami-api"
+gem "puma" # or "webrick", or "thin", "falcon"
+```
+
+And then execute:
+
+```shell
+$ bundle install
+```
+
+Or install it yourself as:
+
+```shell
+$ gem install hanami-api
+```
+
* [Performance](#performance)
+ [Runtime](#runtime)
+ [Memory](#memory)
+ [Requests per second](#requests-per-second)
* [Usage](#usage)
@@ -11,13 +57,16 @@
+ [HTTP methods](#http-methods)
+ [Endpoints](#endpoints)
- [Rack endpoint](#rack-endpoint)
- [Block endpoint](#block-endpoint)
* [String (body)](#string-body)
+ * [Enumerator (body)](#enumerator-body)
* [Integer (status code)](#integer-status-code)
* [Integer, String (status code, body)](#integer-string-status-code-body)
+ * [Integer, Enumerator (status code, body)](#integer-enumerator-status-code-body)
* [Integer, Hash, String (status code, headers, body)](#integer-hash-string-status-code-headers-body)
+ * [Integer, Hash, Enumerator (status code, headers, body)](#integer-hash-enumerator-status-code-headers-body)
+ [Block context](#block-context)
- [env](#env)
- [status](#status)
- [headers](#headers)
- [body](#body)
@@ -25,35 +74,18 @@
- [halt](#halt)
- [redirect](#redirect)
- [back](#back)
- [json](#json)
+ [Scope](#scope)
+ + [Helpers](#helpers)
+ [Rack Middleware](#rack-middleware)
+ + [Streamed Responses](#streamed-responses)
+ [Body Parsers](#body-parsers)
+* [Testing](#testing)
* [Development](#development)
* [Contributing](#contributing)
-## Installation
-
-Add this line to your application's `Gemfile`:
-
-```ruby
-gem "hanami-api"
-```
-
-And then execute:
-
-```shell
-$ bundle install
-```
-
-Or install it yourself as:
-
-```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.
@@ -176,10 +208,20 @@
end
```
It will return `[200, {}, ["Hello, world"]]`
+##### Enumerator (body)
+
+```ruby
+get "/" do
+ Enumerator.new { ... }
+end
+```
+
+It will return `[200, {}, Enumerator]`, see [Streamed Responses](#streamed-responses)
+
##### Integer (status code)
```ruby
get "/" do
418
@@ -196,20 +238,40 @@
end
```
It will return `[401, {}, ["You shall not pass"]]`
+##### Integer, Enumerator (status code, body)
+
+```ruby
+get "/" do
+ [401, Enumerator.new { ... }]
+end
+```
+
+It will return `[401, {}, Enumerator]`, see [Streamed Responses](#streamed-responses)
+
##### Integer, Hash, String (status code, headers, body)
```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"]]`
+##### Integer, Hash, Enumerator (status code, headers, body)
+
+```ruby
+get "/" do
+ [401, {"X-Custom-Header" => "foo"}, Enumerator.new { ... }]
+end
+```
+
+It will return `[401, {"X-Custom-Header" => "foo"}, Enumerator]`, see [Streamed Responses](#streamed-responses)
+
### Block context
When using the block syntax there is a rich API to use.
#### env
@@ -271,10 +333,18 @@
get "/" do
body "Hello, world"
end
```
+Set HTTP response body using a [Streamed Response](#streamed-responses)
+
+```ruby
+get "/" do
+ body Enumerator.new { ... }
+end
+```
+
#### params
Access params for current request
```ruby
@@ -306,10 +376,18 @@
end
```
It sets a Rack response: `[401, {}, ["You shall not pass"]]`
+You can also use a [Streamed Response](#streamed-responses) here
+
+```ruby
+get "/authenticate" do
+ halt(401, Enumerator.new { ... })
+end
+```
+
#### redirect
Redirects request and immediately halts it
```ruby
@@ -362,10 +440,19 @@
user = UserRepository.new.find(params[:id])
json(user, "application/vnd.api+json")
end
```
+If you want a [Streamed Response](#streamed-responses)
+
+```ruby
+get "/users" do
+ users = Enumerator.new { ... }
+ json(users)
+end
+```
+
### Scope
Prefixing routes is possible with routing scopes:
```ruby
@@ -376,10 +463,62 @@
end
```
It will generate a route with `"/api/v1/users"` as path.
+### Helpers
+
+Define helper methods available within the block context.
+Helper methods have access to default utilities available in block context (e.g. `#halt`).
+
+Helpers can be defined inline by passing a block to the `.helpers` method:
+
+```ruby
+require "hanami/api"
+
+class MyAPI < Hanami::API
+ helpers do
+ def redirect_to_root
+ # redirect method is provided by Hanami::API block context
+ redirect "/"
+ end
+ end
+
+ root { "Hello, World" }
+
+ get "/legacy" do
+ redirect_to_root
+ end
+end
+```
+
+Alternatively, `.helpers` accepts a module.
+
+```ruby
+require "hanami/api"
+
+class MyAPI < Hanami::API
+ module Authentication
+ private
+
+ def unauthorized
+ halt(401)
+ end
+ end
+
+ helpers(Authentication)
+
+ root { "Hello, World" }
+
+ get "/secrets" do
+ unauthorized
+ end
+end
+```
+
+You can use `.helpers` multiple times in the same app.
+
### Rack Middleware
To mount a Rack middleware it's possible with `.use`
```ruby
@@ -410,10 +549,60 @@
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"`.
+### Streamed Responses
+
+When the work to be done by the server takes time, it may be a good idea to
+stream your response. For this, you just use an `Enumerator` anywhere you would
+normally use a `String` as body or another `Object` as JSON response. Here's an
+example of streaming JSON data:
+
+```ruby
+scope "stream" do
+ use ::Rack::Chunked
+
+ get "/data" do
+ Enumerator.new do |yielder|
+ data = %w[a b c]
+ data.each do |item|
+ yielder << item
+ end
+ end
+ end
+
+ get "/to_enum" do
+ %w[a b c].to_enum
+ end
+
+ get "/json" do
+ result = Enumerator.new do |yielder|
+ data = %w[a b c]
+ data.each do |item|
+ yielder << item
+ end
+ end
+
+ json(result)
+ end
+end
+```
+
+Note:
+
+* Returning an `Enumerator` will also work without `Rack::Chunked`, it just
+ won't stream but return the whole body at the end instead.
+* Data pushed to `yielder` MUST be a `String`.
+* Streaming does not work with WEBrick as it buffers its response. We recommend
+ using `puma`, though you may find success with other servers.
+* To manual test this feature use a web browser or cURL:
+
+```shell
+$ curl --raw -i http://localhost:2300/stream/data
+```
+
### Body Parsers
Rack ignores request bodies unless they come from a form submission.
If you have an endpoint that accepts JSON, the request payload isn’t available in `params`.
@@ -424,15 +613,71 @@
require "hanami/middleware/body_parser"
use Hanami::Middleware::BodyParser, :json
```
+## Testing
+
+## Unit testing
+You can unit test your `Hanami::API` app by passing a `env` hash to your app's `#call` method.
+
+The keys that (based on the Rack standard) `Hanami::API` uses for routing are:
+* `PATH_INFO`
+* `REQUEST_METHOD`
+
+
+For example, a spec for the basic app in the [Usage section](https://github.com/hanami/api#usage) could be:
+
+```ruby
+require "my_project/app"
+
+RSpec.describe App do
+ describe "#call" do
+ it "returns successfully" do
+ response = subject.call({"PATH_INFO" => "/", "REQUEST_METHOD" => "GET"})
+ expect(response).to eq([200, {}, ["Hello, world"]])
+ end
+ end
+end
+```
+
+## Integration testing
+Add this line to your application's `Gemfile`:
+
+```ruby
+gem "rack-test", group: :test
+```
+
+In a test, load `Rack::Test`:
+
+```ruby
+require "rack/test"
+```
+
+and then, inside your spec/test, include its helper methods:
+
+```ruby
+include Rack::Test::Methods
+```
+
+Then you can use its methods like `get` and `last_response`, e.g.:
+
+```ruby
+it "returns the status 200" do
+ get "/"
+ expect(last_response.status).to eq 200
+end
+```
+
## 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/hanami/api.
+## Copyright
+
+Copyright © 2014-2022 Hanami Team – Released under MIT License.