# [Rack::App](https://github.com/rack-app/rack-app)

![rack-app-logo](/assets/rack-app-logo.png)

[rack-app GPT assistant](https://chat.openai.com/g/g-DOKIpY8aq-rack-app)

`rack-app` is a minimalist web framework that focuses on simplicity and maintainability.
The framework is meant to be used by seasoned web developers.

`rack-app` focus on keeping the dependencies as little as possible,
while allowing writing functional and minimalist rack-based applications,
that will do nothing more than what you defined.

The routing uses a prefix tree,
thus adding a large number of API endpoints won't affect the routing lookup time.

It was inspirited by `Sinatra`, `grape`, and `rack`.
It's used in production, powering back-end APIs running on the public cloud.

## Development Status

The framework is considered stable.
I don't have the plan to feature creep the framework without real-life use-cases,
since most of the custom edge cases can be resolved with composition.

The next time it will receive further updates,
when rack provides a finalized support for http2.

If you have an issue, I weekly check the issues tab,
answer and reply, or implement a fix for it.

Since the framework's only dependency is the `rack` gem,
I don't have to update the codebase too often.

Cheers and Happy Coding!

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'rack-app'
```

And then execute:

    $ bundle

Or install it yourself as:

    $ gem install rack-app


## Is it Production ready?

Yes, it's already powering Heroku hosted micro-services.

## Principles

* Keep It Simple
* No Code bloat
* No on run time processing, or keep at the bare minimum
* Fully BDD (Behaviour Driven Design)
  * built-in test module to ease the development with easy to use tests
* Easy to Learn
  * rack-app use well known and easy to understand conventions, such as sinatra like DSL
* Principle Of Least Surprise
* Modular design
* the Only dependency is rack, nothing more
* Open development
* Try to create Examples for every feature

## Features

* Easy to understand syntax
  * module method level endpoint definition inspirited heavily by the Sinatra DSL
  * unified error handling
  * syntax sugar for default header definitions
  * namespaces for endpoint request path declarations so it can be dry and unified
* no Class method bloat, so you can enjoy pure ruby without any surprises
* App mounting so you can create separated controllers for different task
* Streaming
* O(log(n)) lookup routing
  * allows as many endpoint registrations to you as you want, without impact on route lookup speed
* only basic sets for instance method level for the must need tools, such as params, payload
* Simple to use class level response serializer
  * so you can choose what type of serialization you want without any enforced convention
* static file serving so you can mount even filesystem-based endpoints too
* built-in testing module so your app can be easily written with BDD approach
* made with minimalism in mind so your app can't rely on the framework when you implement business logic
  * if you need something, you should implement it without any dependency on a web framework, rack-app only mean to be to provide you with easy to use interface to the web layer, nothing less and nothing more
* per endpoint middleware definitions
  * you can define middleware stack before endpoints and it will only apply to them, similar like protected method workflow
* File Upload and file download efficiently and elegantly with minimal memory consuming
  * note that this is not only memory friendly way pure rack solution, but also 2x faster than the usual solution which includes buffering in memory
* params validation with ease

## Under the hood

rack-app's router relies on a tree structure which makes heavy use of *common prefixes*,
it is basically a *compact* [*prefix tree*](https://en.wikipedia.org/wiki/Trie) (or just [*Radix tree*](https://en.wikipedia.org/wiki/Radix_tree)).
Nodes with a common prefix also share a common parent.

## Contributors

* **[Daniel Nagy](https://github.com/thilonel)**

  * Serializer MVP implementation

* **[Daniel Szpisjak](https://github.com/felin-arch)**

  * Pimp up the website descriptions

* **[Jeremy Evans](https://github.com/jeremyevans)**

  * suggestion for application stand up speed optimization

* **[David Bush](https://github.com/disavowd)**

  * [wrote an awesome article](https://www.sitepoint.com/rack-app-a-performant-and-pragmatic-web-microframework/) about the project

* **[TheSmartnik](https://github.com/TheSmartnik)**

  * Clarify examples in the documentation

## [Contributing](CONTRIBUTING.md)

## Usage

#### basic

```ruby

require 'rack/app'

class App < Rack::App

  desc 'some hello endpoint'
  get '/hello' do
    'Hello World!'
  end

end

```

#### complex

```ruby

require 'rack/app'

class App < Rack::App

  mount SomeAppClass

  headers 'Access-Control-Allow-Origin' => '*',
          'Access-Control-Expose-Headers' => 'X-My-Custom-Header, X-Another-Custom-Header'

  serializer do |object|
    object.to_s
  end

  desc 'some hello endpoint'
  validate_params do
    required 'words', :class => Array, :of => String, :desc => 'some word', :example => ['pug']
    optional 'word', :class => String, :desc => 'one word', :example => 'pug'
    optional 'boolean', :class => :boolean, :desc => 'boolean value', :example => true
  end
  get '/hello' do
    puts(params['words'])

    'Hello World!'
  end

  namespace '/users' do

    desc 'some restful endpoint'
    get '/:user_id' do
      response.status = 201
      params['user_id'] #=> restful parameter :user_id
      say #=> "hello world!"
    end

  end

  desc 'some endpoint that has error and will be rescued'
  get '/make_error' do
    raise(StandardError,'error block rescued')
  end

  def say
    "hello #{params['user_id']}!"
  end

  error StandardError, NoMethodError do |ex|
    {:error=>ex.message}
  end

  root '/hello'

  get '/stream' do
    stream do |out|
      out << 'data row'
    end
  end

end

```

you can access Rack::Request with the request method and
Rack::Response as a response method.

By default, if you don't write anything to the response 'body' the endpoint block logic return will be used

### Frontend Example

if you don't mind extending your dependency list then you can use the front_end extension for creating template-based web applications.

```ruby
require 'rack/app'
require 'rack/app/front_end' # You need to add `gem 'rack-app-front_end'` to your Gemfile

class App < Rack::App

  apply_extensions :front_end

  helpers do

    def method_that_can_be_used_in_template
      'hello world!'
    end

  end

  # use ./app/layout.html.erb as layout, this is optionable
  layout 'layout.html.erb'

  # at '/' the endpoint will serve (render)
  # the ./app/index.html content as response body and wrap around with layout if the layout is given
  get '/' do
    render 'index.html'
  end

end
```

this example expects an "app" folder next to the "app.rb" file that included templates being used such as layout.html.erb and index.html.

## Testing

for testing use rack/test or the bundled testing module for writing unit test for your rack application

```ruby

require 'spec_helper'
require 'rack/app/test'

describe App do

  include Rack::App::Test

  rack_app described_class

  describe '/hello' do
    # example for params and headers and payload use
    subject { get(url: '/hello', params: {'dog' => 'meat'}, headers: {'X-Cat' => 'fur'}, payload: 'some string') }

    it { expect(subject.status).to eq 200 }

    it { expect(subject.body).to eq "Hello World!" }
  end

  describe '/users/:user_id' do
    # restful endpoint example
    subject { get(url: '/users/1234') }

    it { expect(subject.body).to eq 'hello 1234!'}

    it { expect(subject.status).to eq 201 }

  end

  describe '/make_error' do
    # error handled example
    subject { get(url: '/make_error') }

    it { expect(subject.body).to eq '{:error=>"error block rescued"}' }
  end

end


```

## Example Apps To start with

* [Official website How To examples](https://github.com/rack-app/rack-app/wiki)

* [Rack::App Team Github repositories](https://github.com/rack-app)

* [Basic](https://github.com/rack-app/rack-app-example-basic)
  * bare-bone simple example app

* [Escher Authorized Api](https://github.com/rack-app/rack-app-example-escher)
  * complex authorization for corporal level API use


## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run 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](https://rubygems.org).

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/rack-app/rack-app This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.

## License and Copyright

Rack::App is free software released under the [Apache License V2](https://opensource.org/licenses/Apache-2.0) License.
The logo was designed by Zsófia Gebauer. It is Copyright © 2015 Adam Luzsi. All Rights Reserved.
