# Tynn [](https://travis-ci.org/frodsan/tynn) [](https://gemnasium.com/github.com/frodsan/tynn) [](https://codeclimate.com/github/frodsan/tynn) [](https://gitter.im/frodsan/tynn?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) A thin library for web development in Ruby. * [Getting Started](#getting-started) * [Assumptions](#assumptions) * [Installation](#installation) * [Hello World!](#hello-world) * [Under the Hood](#under-the-hood) * [Tynn on Rack](#tynn-on-rack) * [Routing Basics](#routing-basics) * [Managing Request and Response](#managing-request-and-response) * [Redirecting a Request](#redirecting-a-request) * [Halting a Request](#halting-a-request) * [Extending Tynn](#extending-tynn) * [Middleware](#middleware) * [Plugins](#plugins) * [Settings](#settings) * [Default Plugins](#default-plugins) * [Environments](#environments) * [Method Override](#method-override) * [Static Files](#static-files) * [Security](#security) * [Testing](#testing) * [API Reference](http://api.tynn.xyz/2.0.0) * [Changelog](#changelog) * [Development](#development) * [Contributing](#contributing) * [Build History](#build-history) * [License](#license) **NOTE. There is an online version of this README at http://tynn.xyz/.** ## Getting Started Tynn is a minimal and flexible library for building JSON web services and web applications in Ruby. It's designed with two main goals: simplicity and extensibility. Tynn offers the essentials to handle HTTP requests and also includes mechanisms to easily extend its functionality through [plugins](#plugins) and [middleware][middleware]. Tynn ships with a set of [default plugins](#default-plugins) that you can combine to meet your needs. Tynn is not a framework. There is no built-in support for connecting to databases or a common application architecture. This gives you the freedom to choose the appropiate tools for the job at hand. Like most web libraries in Ruby, Tynn is built on top of [Rack], a Ruby webserver interface. Because of this, it has the benefits of using existing libraries and a variety of web servers for free. Tynn takes design cues from other web libraries like [Rails], [Sinatra], and [Cuba]. Under the hood, it uses [Syro], a fast router for web applications. ### Assumptions This section serves as a high-level introduction to the core features of Tynn. It also covers installation and walks through the creation of a simple application. To get the most out of it, it's recommended to have a basic knowledge about Ruby and HTTP. ### Installation Installing Tynn is pretty straightforward. From the command line, use the `gem` command: ``` $ gem install tynn ``` If you prefer to use [Bundler] instead, set up the environment with: ``` $ bundle init ``` Then, update the contents of the generated Gemfile to: ```ruby source "https://rubygems.org" gem "tynn", "~> 2.0" ``` Finally, install the dependencies with: ``` $ bundle install ``` Now you can create your first Tynn app! ### Hello World! Let's start with a minimal application. Open your preferred text editor and enter the following code: ```ruby require "tynn" Tynn.define do on root? do get do res.write("Hello World!") end end end ``` Save this file as `app.rb`. Once you've done so, create another file called `config.ru` with the contents shown below: ```ruby require File.expand_path("app", __dir__) run(Tynn) ``` You already have a functional application! The syntax is very readable, but we'll discuss the details in a moment. To see it in action, you need to start a web server. You can do this by typing `rackup` in the command line.  > **NOTE:** To stop the web server, hit `Ctrl+C` in the terminal window where it's running. To verify that the server has stopped you should see your command prompt cursor again. Now open a browser window and navigate to http://localhost:9292 to see the greeting message.  ### Under the hood Tynn is all about routing. Routing determines how an application should respond to a client request to a URI (or path) and a specific HTTP request method. In our previous example, that would be the root path (`/`) and the HTTP GET method. To get a look on what's happening under the hood, let's see line 4: ```ruby on root? do # ... end ``` That's a little taste of what we call routing matchers in Tynn. The purpose of the `on` matcher is to execute the given block only if the given argument returns `true`. In our case, `root?` returns `true` because we are accessing the root path (`/`) from our browser. One line below there is another type of matcher: ```ruby get do res.write("Hello World!") end ``` Tynn also includes methods for matching all the HTTP verbs (GET, POST, etc.). By default, if there are no other matchers before a verb matcher, it automatically checks that the current request is for the root path. The above example can be written as: ```ruby Tynn.define do get do res.write("Hello World!") end end ``` > **Note.** If you're not familiar with the HTTP methods, check out this [Wikipedia page](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods). ### Tynn on Rack The last part of the puzzle is the `config.ru` file. As we mentioned earlier, Tynn is built on top of Rack ... but what is Rack? Rack deals with HTTP requests. It provides a minimal interface between web servers supporting Ruby and Ruby web frameworks. Without Rack, Tynn would have to implement its own handler for each Ruby web server. To run the hello world application you used `rackup`, one of the tools that comes with Rack. To use `rackup`, you need to supply a `config.ru` file. This file connects the Rack interface with our Tynn application through the `run` method. ```ruby run(Tynn) ``` Rack also figures out which server you have available in your system. When we used `rackup`, it fired up WEBrick, a web server built into Ruby by default. ``` $ rackup [2016-05-14 00:25:11] INFO WEBrick 1.3.1 ``` Most popular web servers, like [Puma], [Unicorn] or [Thin], have built-in support for Rack, and thus support Tynn too. You can read more about Rack visiting their home page: http://rack.github.io/. ## Routing basics This section covers the routing features of Tynn, such as route definitions, composing applications, and so on. ## Managing Request and Response This section covers the many features Tynn provides to handle requests and responses. ### Redirecting a Request To redirect a request to a different location, use `res.redirect`. ```ruby res.redirect("/pricing") ``` To redirect to a different site, use a fully-qualified URL. ```ruby res.redirect("https://google.com") ``` By default, the status code is set to `302` (Found). To set a different status code, pass an integer as a second parameter. ```ruby res.redirect("/projects/1", 301) ``` ### Halting a Request To immediately stop a request within a route, you can use the `halt` method. ```ruby halt([status, headers, body]) ``` Use `res.finish` to return a response as per Rack's specification. ```ruby if current_user.nil? res.redirect("/login") halt(res.finish) end # This won't be reached if current_user is nil ``` ## Extending Tynn ### Middleware Tynn runs on [Rack](https://github.com/rack/rack). Therefore it is possible to use Rack middleware in Tynn. This is how you add a middleware (for example `YourMiddleware`) to your app: ```ruby Tynn.use(YourMiddleware) ``` You can use any Rack middleware to your app, it is not specific to Tynn. You can find a list of Rack middleware [here][middleware]. [middleware]: https://github.com/rack/rack/wiki/list-of-middleware ### Plugins A way to extend Tynn is to use the plugin API. A plugin is just a module which can contain any of the following rules: - If a `ClassMethods` module is defined, it extends the application class. - If a `InstanceMethods` module is defined, it's included in the application. - If a `setup` method is defined, it will be called last. This method can be used to configure the plugin. The following is a complete example of the plugin API. ```ruby require "valuta" module CurrencyHelper def self.setup(app, currency: "$") self.currency = currency end module ClassMethods def currency @currency end def currency=(currency) @currency = currency end end module InstanceMethods def to_currency(value) Valuta.convert(value, prefix: self.class.currency) end end end ``` To load the plugin into the application, use the `plugin` method. ```ruby App.plugin(CurrencyHelper, currency: "$") ``` Here is the plugin in action: ```ruby App.currency # => "$" App.define do get do res.write(to_currency(4567)) end end # GET / => 200 $4,567 ``` ### Settings Each application has a `settings` hash where configuration can be stored. By default, settings are inherited. ```ruby Tynn.set(:layout, "layout") class Guests < Tynn; end class Users < Tynn; end class Adminds < Tynn; end Users.set(:layout, "users/layout") Admins.set(:layout, "admins/layout") Guests.settings[:layout] # => "layout" Users.settings[:layout] # => "users/layout" Admins.settings[:layout] # => "admins/layout" ``` This features comes in handy when authoring plugins. ```ruby module CurrencyHelper module ClassMethods def currency=(currency) set(:currency, currency) end def currency settings.fetch(:currency, "$") end end end ``` ## Default Plugins Tynn ships with a set of default plugins: | Name | Description | --------------------- | ----------------------------------------------------------------- | [Tynn::Environment] | Adds helper methods to get and check the current environment. | [Tynn::JSON] | Adds helper methods for json generation. | [Tynn::Render] | Adds support for rendering templates through different engines. | [Tynn::Session] | Adds simple cookie based session management. | [Tynn::Static] | Adds support for static files (javascript files, images, etc.). The following sections cover the default plugins and extensions shipped with Tynn. ### Environments Tynn ships with [Tynn::Environment] to set and check the current environment for the application. ```ruby require "tynn" require "tynn/environment" Tynn.plugin(Tynn::Environment) ``` The default environment is based on the `RACK_ENV` environment variable. ```ruby ENV["RACK_ENV"] # => "test" Tynn.environment # => :test ``` If `ENV["RACK_ENV"]` is `nil`, the default value is `:development`. ```ruby Tynn.environment # => :development ``` To change the current environment, use the `environment=` method. ```ruby Tynn.environment = :development Tynn.environment # => :development ``` To check the current environment, use: `development?`, `test?`, `production?` or `staging?`. ```ruby Tynn.plugin(Tynn::SSL) if Tynn.production? ``` Perform operations on specific environments with the `configure` method. ```ruby Tynn.configure(:development) do |app| app.use(Tynn::Static, %w(/js /css /images)) end Tynn.configure(:production) do |app| app.use(Tynn::SSL) end ``` ### Method Override HTML Forms only support GET and POST requests. To perform other actions such as PUT, PATCH or DELETE, use the [Rack::MethodOverride] middleware. Note that there is no need to add any new dependencies to the application as it's included in Rack already. ```ruby Tynn.use(Rack::MethodOverride) ``` This uses a POST form to simulate a request with a non-supported method. In order to succeed, a hidden input field, with the name `_method` and the method name as the value, needs to be included. The following example simulates a PUT request. ```html
``` Now, this will trigger the `put` matcher in the application. ```ruby Posts.define do put do post.update(req.params["post"]) end end ``` ### Static Files Tynn ships with [Tynn::Static] to serve static files such as images, CSS, JavaScript and others. ```ruby require "tynn" require "tynn/static" Tynn.plugin(Tynn::Static, %w(/js /css /images)) ``` By default, static files are served from the folder `public` in the current directory. You can specify a different location by passing the `:root` option: ```ruby Tynn.plugin(Tynn::Static, %w(/js /css /images), root: "assets") ``` As you can see in the table below, the name of static directory is not included in the URL because the files are looked up relative to that directory. | File | URL | | ---------------------------- | -------------------------------------- | | ./public/js/application.js | http://example.org/js/application.js | | ./public/css/application.css | http://example.org/css/application.css | | ./public/images/logo.png | http://example.org/images/logo.png | It's important to mention that the path of the static directory path is relative to the directory where you run the application. If you run the application from another directory, it's safer to use an absolute path: ```ruby Tynn.plugin( Tynn::Static, %w(/js /css /images), root: File.expand_path("public", __dir__) ) ``` ## Security ## Testing Tynn ships with [Tynn::Test], a simple helper class to simulate requests to your application. ```ruby require "tynn" require "tynn/test" Tynn.define do root do res.write("hei") end end app = Tynn::Test.new app.get("/") 200 == app.res.status # => true "hei" == app.res.body # => true ``` [Tynn::Test] is test-framework agnostic. The following example uses [Minitest]: ```ruby require "minitest/autorun" require "tynn/test" class GuestsRouteTest < Minitest::Test def setup @app = Tynn::Test.new end def test_home @app.get("/") assert_equal 200, @app.res.status assert_equal "Hello World!", @app.res.body assert_equal "text/html", @app.res["Content-Type"] end end ``` If this is not of your flavor, you can use any Rack-based testing library or framework, like: [Rack::Test] or [Capybara]. ## Changelog To learn about new features, bug fixes, and changes, please refer to the [CHANGELOG](https://github.com/frodsan/tynn/blob/master/CHANGELOG.md). ## Development Fork the project with: ``` $ git clone git@github.com:frodsan/tynn.git ``` To install dependencies, use: ``` $ bundle install ``` To run the test suite, do: ``` $ rake test ``` ## Contributing Use [GitHub Issues](https://github.com/frodsan/tynn/issues) for reporting bugs, discussing features and general feedback. If you've found a problem in Tynn, be sure to check the [past issues](https://github.com/frodsan/tynn/issues?state=closed) before open a new one. ## Build History [](https://travis-ci.org/frodsan/tynn/builds) ## License Tynn is released under the [MIT License](http://www.opensource.org/licenses/MIT). [bundler]: http://bundler.io/ [capybara]: https://github.com/jnicklas/capybara [cuba]: http://cuba.is/ [minitest]: https://github.com/seattlerb/minitest [puma]: http://puma.io/ [rack]: http://rack.github.io/ [rack::test]: https://github.com/brynary/rack-test [rack::methodoverride]: http://www.rubydoc.info/github/rack/rack/Rack/MethodOverride [rails]: http://rubyonrails.org/ [sinatra]: http://www.sinatrarb.com/ [syro]: http://soveran.github.io/syro/ [thin]: http://code.macournoyer.com/thin/ [tynn::environment]: http://api.tynn.xyz/2.0.0/Tynn/Environment.html [tynn::json]: http://api.tynn.xyz/2.0.0/Tynn/JSON.html [tynn::render]: http://api.tynn.xyz/2.0.0/Tynn/Render.html [tynn::session]: http://api.tynn.xyz/2.0.0/Tynn/Session.html [tynn::static]: http://api.tynn.xyz/2.0.0/Tynn/Static.html [tynn::test]: http://api.tynn.xyz/2.0.0/Tynn/Test.html [unicorn]: https://unicorn.bogomips.org/