<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Elias Perez</title>
    <description>The latest articles on DEV Community by Elias Perez (@eliasjpr).</description>
    <link>https://dev.to/eliasjpr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F52927%2Fc0ddedee-9338-4559-a7c0-c3b25c620611.jpeg</url>
      <title>DEV Community: Elias Perez</title>
      <link>https://dev.to/eliasjpr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eliasjpr"/>
    <language>en</language>
    <item>
      <title>Introduction to the Amber Web Framework And its Out-of-the-Box Features</title>
      <dc:creator>Elias Perez</dc:creator>
      <pubDate>Sun, 21 Jan 2018 19:50:32 +0000</pubDate>
      <link>https://dev.to/eliasjpr/introduction-to-the-amber-web-framework-and-its-out-of-the-box-features-39c6</link>
      <guid>https://dev.to/eliasjpr/introduction-to-the-amber-web-framework-and-its-out-of-the-box-features-39c6</guid>
      <description>&lt;p&gt;
  &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdocelic%2Famber-introduction%2Fmaster%2Famber.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fdocelic%2Famber-introduction%2Fmaster%2Famber.png"&gt;&lt;/a&gt;
  &lt;/p&gt;
&lt;h3&gt;
&lt;strong&gt;Introduction to the Amber Web Framework&lt;/strong&gt;&lt;br&gt;
  And its Out-of-the-Box Features&lt;/h3&gt;
  &lt;p&gt;
    &lt;sup&gt;
      Amber makes building web applications easy, fast, and enjoyable.
      
    &lt;/sup&gt;
  &lt;/p&gt;
  

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Amber&lt;/strong&gt; is a web application framework written in &lt;a href="http://www.crystal-lang.org" rel="noopener noreferrer"&gt;Crystal&lt;/a&gt;. Homepage is at &lt;a href="https://amberframework.org/" rel="noopener noreferrer"&gt;amberframework.org&lt;/a&gt;, docs are on &lt;a href="https://docs.amberframework.org" rel="noopener noreferrer"&gt;Amber Docs&lt;/a&gt;, Github repository is at &lt;a href="https://github.com/amberframework/amber" rel="noopener noreferrer"&gt;amberframework/amber&lt;/a&gt;, and the chat is on &lt;a href="https://gitter.im/amberframework/amber" rel="noopener noreferrer"&gt;Gitter&lt;/a&gt; or FreeNode IRC channel #amber.&lt;/p&gt;

&lt;p&gt;Amber is inspired by Kemal, Rails, Phoenix and other frameworks. It is simple to get used to, and much more intuitive than frameworks like Rails. (But it does inherit some concepts from Rails that are good.)&lt;/p&gt;

&lt;p&gt;This document is here to describe everything that Amber offers out of the box, sorted in a logical order and easy to consult repeatedly over time. The Crystal level is not described; it is expected that the readers coming here have a formed understanding of &lt;a href="https://crystal-lang.org/docs/overview/" rel="noopener noreferrer"&gt;Crystal and its features&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  Installation
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/amberframework/amber
&lt;span class="nb"&gt;cd &lt;/span&gt;amber
make &lt;span class="c"&gt;# The result of 'make' is one file -- command line tool bin/amber&lt;/span&gt;

&lt;span class="c"&gt;# To install the file, or to symlink the system-wide executable to current directory, run one of:&lt;/span&gt;
make &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="c"&gt;# default PREFIX is /usr/local&lt;/span&gt;
make &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/stow/
make force_link &lt;span class="c"&gt;# can also specify PREFIX=...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After installation or linking, &lt;code&gt;amber&lt;/code&gt; is the command you will be using&lt;br&gt;
for creating or managing Amber apps.&lt;/p&gt;
&lt;h1&gt;
  
  
  Creating New Amber App
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;amber new &amp;lt;app_name&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt; DATABASE] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-t&lt;/span&gt; TEMPLATE_LANG] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt; ORM_MODEL] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--deps&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Supported databases are &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; (pg, default), &lt;a href="https://www.mysql.com/" rel="noopener noreferrer"&gt;MySQL&lt;/a&gt; (mysql), and &lt;a href="https://sqlite.org/" rel="noopener noreferrer"&gt;SQLite&lt;/a&gt; (sqlite).&lt;/p&gt;

&lt;p&gt;Supported template languages are &lt;a href="https://github.com/jeromegn/slang" rel="noopener noreferrer"&gt;slang&lt;/a&gt; (default) and &lt;a href="https://crystal-lang.org/api/0.21.1/ECR.html" rel="noopener noreferrer"&gt;ecr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Slang is extremely elegant, but very different from the traditional perception of HTML.&lt;br&gt;
ECR is HTML-like, very similar to Ruby ERB, and more than mediocre when compared to slang, but it may be the best choice for your application if you intend to use some HTML site template (from e.g. &lt;a href="https://themeforest.net/" rel="noopener noreferrer"&gt;themeforest&lt;/a&gt;) whose pages are in HTML + CSS or SCSS. (Or you could also try &lt;a href="https://github.com/docelic/html2slang/" rel="noopener noreferrer"&gt;html2slang&lt;/a&gt; which converts HTML pages into slang.)&lt;/p&gt;

&lt;p&gt;In any case, you can combine templates in various languages in a project, and regardless of the language, have in mind that the templates are compiled into the application. There is no lookup on disk or choosing between available templates during runtime. This makes templates extremely fast, as well as read-only which is a very welcome side-benefit!&lt;/p&gt;

&lt;p&gt;Supported ORM models are &lt;a href="https://github.com/amberframework/granite-orm" rel="noopener noreferrer"&gt;granite&lt;/a&gt; (default) and &lt;a href="https://github.com/Crecto/crecto" rel="noopener noreferrer"&gt;crecto&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Granite is a very nice and simple, effective ORM model, where you mostly write your own SQL (i.e. all search queries typically look like YourModel.all("WHERE field1 = ? AND field2 = ?", [value1, value2])). But it also has belongs/has relations, and some other little things. (If you have by chance known and loved &lt;a href="http://search.cpan.org/~tmtm/Class-DBI-v3.0.17/lib/Class/DBI.pm" rel="noopener noreferrer"&gt;Class::DBI&lt;/a&gt; for Perl, it might remind you of it in some ways.)&lt;/p&gt;

&lt;p&gt;Supported migrations engine is &lt;a href="https://github.com/juanedi/micrate" rel="noopener noreferrer"&gt;micrate&lt;/a&gt;. Micrate is very simple and you basically write raw SQL in your migrations. There are just two keywords in the migration file which give instructions whether the SQLs that follow pertain to migrating up or down. These keywords are "-- +micrate Up" and "-- +micrate Down".&lt;/p&gt;

&lt;p&gt;If argument --deps is provided, Amber will automatically run &lt;code&gt;crystal&lt;br&gt;
deps&lt;/code&gt; in the new directory to install shards.&lt;/p&gt;
&lt;h1&gt;
  
  
  Running the App
&lt;/h1&gt;

&lt;p&gt;The app can be started as soon as you have created it and ran &lt;code&gt;crystal deps&lt;/code&gt; in the app directory.&lt;br&gt;
(It is not necessary to run deps if you have invoked &lt;code&gt;amber new&lt;/code&gt; with the argument --deps; in that case Amber did it for you.)&lt;/p&gt;

&lt;p&gt;To run it, you can use a couple different approaches. Some are of course suitable for development, some for production, etc.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For development, clean and simple - compiles and runs your app:&lt;/span&gt;
crystal src/&amp;lt;app_name&amp;gt;.cr

&lt;span class="c"&gt;# For development, clean and simple - compiles and runs your app, but&lt;/span&gt;
&lt;span class="c"&gt;# also watches for changes in files and rebuilds/re-runs automatically.&lt;/span&gt;
amber watch

&lt;span class="c"&gt;# For production, compiles app with optimizations and places it in bin/app.&lt;/span&gt;
&lt;span class="c"&gt;# Crystal by default compiles using 8 threads (tune if needed with --threads NUM)&lt;/span&gt;
crystal build &lt;span class="nt"&gt;--no-debug&lt;/span&gt; &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--verbose&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; bin/&amp;lt;app_name&amp;gt; src/&amp;lt;app_name&amp;gt;.cr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that Granite currently has problems in edge cases. For example, if you create a new model but do not specify any fields for it, then until you add at least one field, Amber won't start due to a compile error (&lt;a href="https://github.com/amberframework/granite-orm/issues/112" rel="noopener noreferrer"&gt;#112&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Amber by default uses a feature called "port reuse" available in newer Linux kernels. If you get an error "setsockopt: Protocol not available", it means your kernel does not have it. Please edit &lt;code&gt;config/environments/development.yml&lt;/code&gt; and set "port_reuse" to false.&lt;/p&gt;

&lt;h1&gt;
  
  
  Building the App and Troubleshooting
&lt;/h1&gt;

&lt;p&gt;The application is always built, regardless of whether one is using the Crystal command 'run' (the default) or 'build'. It is just that in run mode, the resulting binary won't be saved to a file, but will be executed and later discarded.&lt;/p&gt;

&lt;p&gt;For faster build speed, development versions are compiled without the --release flag. With the --release flag, the compilation takes noticeably longer, but the resulting binary has incredible performance.&lt;/p&gt;

&lt;p&gt;Crystal caches partial results of the compilation (*.o files etc.) under &lt;code&gt;~/.cache/crystal/&lt;/code&gt; for faster subsequent builds. This directory is also where temporary binaries are placed when one runs programs with &lt;code&gt;crystal [run]&lt;/code&gt; rather than &lt;code&gt;crystal build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sometimes building the app will fail on the C level because of missing header files or libraries. If Crystal doesn't print the actual C error, it will at least print the compiler line that caused it.&lt;/p&gt;

&lt;p&gt;The best way to see the actual error from there is to copy-paste the command printed and run it manually in the terminal. The error will be shown and from there the cause will be determined easily.&lt;/p&gt;

&lt;p&gt;There are some issues with the &lt;code&gt;libgc&lt;/code&gt; library here and there. Crystal comes with built-in &lt;code&gt;libgc&lt;/code&gt;, but it may conflict with the system one. In my case the solution was to install and then remove package &lt;code&gt;libgc-dev&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  REPL
&lt;/h1&gt;

&lt;p&gt;Often times, it is very useful to enter an interactive console (think of IRB shell) with all application classes initialized etc. In Ruby this would be done with IRB or with a command like &lt;code&gt;rails console&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Due to its nature, Crystal does not have a free-form &lt;a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop" rel="noopener noreferrer"&gt;REPL&lt;/a&gt;, but you can save and execute scripts in the context of the application. One way to do it is via command &lt;code&gt;amber x [filename]&lt;/code&gt;. This command will allow you to type or edit the contents, and then execute the script.&lt;/p&gt;

&lt;p&gt;Another, more professional way to do it is via standalone REPL-like script tools &lt;a href="https://github.com/elorest/cry" rel="noopener noreferrer"&gt;cry&lt;/a&gt; and &lt;a href="https://github.com/crystal-community/icr" rel="noopener noreferrer"&gt;icr&lt;/a&gt;. &lt;code&gt;cry&lt;/code&gt; began as an experiment and a predecessor to &lt;code&gt;amber x&lt;/code&gt;, but now offers additional functionality such as repeatedly editing and running the script if &lt;code&gt;cry -r&lt;/code&gt; is invoked.&lt;/p&gt;

&lt;p&gt;In any case, running a script "in application context" simply means requiring &lt;code&gt;config/application.cr&lt;/code&gt; (and through it, &lt;code&gt;config/**&lt;/code&gt;), Therefore, be sure to list all your requires in &lt;code&gt;config/application.cr&lt;/code&gt; so that everything works as expected.&lt;/p&gt;

&lt;h1&gt;
  
  
  File Structure
&lt;/h1&gt;

&lt;p&gt;So, at this point you might be wanting to know what's placed where in an Amber application. The default structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./config/                  - All configuration
./config/application.cr    - Main configuration file
./config/initializers/     - Initializers (files loaded at very beginning)
./config/environments/     - Environment-specific YAML configurations
./config/webpack/          - Webpack (asset bundler) configuration
./config/routes.cr         - All routes
./db/migrations/           - All DB migration files (created with 'amber g migration ...')
./public/                  - The "public" directory for static files
./public/dist/             - Directory inside "public" for generated files and bundles
./public/dist/images/
./src/                     - Main source directory, with &amp;lt;app_name&amp;gt;.cr being the main/entry file
./src/controllers/         - All controllers
./src/models/              - All models
./src/views/layouts/       - All layouts
./src/views/               - All views
./src/views/home/          - Views for HomeController (path "/")
./src/assets/              - Static assets which will be bundled and placed into ./public/dist/
./src/assets/stylesheets/
./src/assets/fonts/
./src/assets/images/
./src/assets/javascripts/
./spec/                    - Tests (named *_spec.cr)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I prefer to have some of these directories accessible directly in the root directory of the application and to have the config directory named &lt;code&gt;etc&lt;/code&gt;, so I run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ln -sf config etc
ln -sf src/assets
ln -sf src/controllers
ln -sf src/models
ln -sf src/views
ln -sf src/views/layouts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Database Commands
&lt;/h1&gt;

&lt;p&gt;Amber provides a group of commands under the 'db' group to allow working with the database. The simple commands you will most probably want to run just to see basic things working are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;amber db create
amber db status
amber db version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before these commands will work, you will need to configure database&lt;br&gt;
credentials:&lt;/p&gt;

&lt;p&gt;First, create a user to access the database. For PostgreSQL, this is done by invoking something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;su - postgres
&lt;span class="nv"&gt;$ &lt;/span&gt;createuser &lt;span class="nt"&gt;-dElPRS&lt;/span&gt; myuser
Enter password &lt;span class="k"&gt;for &lt;/span&gt;new role: 
Enter it again: 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, edit &lt;code&gt;config/environments/development.yml&lt;/code&gt; and configure "database_url:" to match your settings. If nothing else, the part that says "postgres:@" should be replaced with "yourusername:yourpassword@".&lt;/p&gt;

&lt;p&gt;And then try the database commands from the beginning of this section.&lt;/p&gt;

&lt;p&gt;Please note that for the database connection to succeed, all parameters must be correct (hostname, port, username, password, database name), the database server must be accessible, and the database must actually exist (unless you are invoking 'amber db create' to create it). In case of &lt;em&gt;any error in any of the stages&lt;/em&gt; of connecting to the database, the error message will be very terse and just say "Connection unsuccessful: ". The solution is simple, though - simply use the printed database_url to manually attempt a connection to the database with the same parameters, and the problem will most likely quickly reveal itself.&lt;/p&gt;

&lt;p&gt;Please note that the environment files for non-production environment are given in plain text. Environment file for the production environment is encrypted for additional security and can be seen or edited by invoking &lt;code&gt;amber encrypt&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Routes
&lt;/h1&gt;

&lt;p&gt;Routes are very easy to understand. Routes connect HTTP methods (and the paths with which they were invoked) to controllers and methods on the Amber side.&lt;/p&gt;

&lt;p&gt;Amber includes a wonderful command &lt;code&gt;amber routes&lt;/code&gt; to display current routes. By default, the routes table looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;amber routes

╔══════╦═══════════════════════════╦════════╦══════════╦═══════╦═════════════╗
║ Verb | Controller                | Action | Pipeline | Scope | URI Pattern ║
╠──────┼───────────────────────────┼────────┼──────────┼───────┼─────────────╣
║ get  | Amber::Controller::Static | index  | static   |       | /&lt;span class="k"&gt;*&lt;/span&gt;          ║
╠──────┼───────────────────────────┼────────┼──────────┼───────┼─────────────╣
║ get  | HomeController            | index  | web      |       | /           ║
╚══════╩═══════════════════════════╩════════╩══════════╩═══════╩═════════════╝
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this example, we see that a "GET /" request will instantiate&lt;br&gt;
HomeController and then call method index() in it. The return value of&lt;br&gt;
the method will be returned to the client.&lt;/p&gt;

&lt;p&gt;Similarly, here's an example of a route that would route HTTP POST requests to "/registration" to the method create() in class RegistrationController:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;post "/registration", RegistrationController, :create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standard HTTP verbs (GET, HEAD, POST, PUT, PATCH, DELETE) by convention go to standard methods on the controllers (show, new, create, edit, update, destroy). However, there is nothing preventing you from routing URLs to any methods you want in the controllers, such as we've done with "index" above.&lt;/p&gt;

&lt;p&gt;Websocket routes are supported too.&lt;/p&gt;

&lt;p&gt;The DSL language specific to &lt;code&gt;config/routes.cr&lt;/code&gt; file is defined in &lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/dsl/router.cr" rel="noopener noreferrer"&gt;dsl/router.cr&lt;/a&gt; and &lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/dsl/server.cr" rel="noopener noreferrer"&gt;dsl/server.cr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It gives you the following top-level commands/blocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Define a pipeline
pipeline :name do
  ...
end

# Group a set of routes
routes :name, "path" do
  ...
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="no"&gt;Amber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="ss"&gt;:web&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Plug is the method used to connect a pipe (middleware)&lt;/span&gt;
    &lt;span class="c1"&gt;# A plug accepts an instance of HTTP::Handler&lt;/span&gt;
    &lt;span class="n"&gt;plug&lt;/span&gt; &lt;span class="no"&gt;Amber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Pipe&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;routes&lt;/span&gt; &lt;span class="ss"&gt;:web&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;HomeController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:index&lt;/span&gt;    &lt;span class="c1"&gt;# Routes to HomeController::index()&lt;/span&gt;
    &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;PageController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:test&lt;/span&gt; &lt;span class="c1"&gt;# Routes to PageController::test()&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within 'routes', the following commands are available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;resources&lt;/code&gt; is a macro defined as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;    &lt;span class="k"&gt;macro&lt;/span&gt; &lt;span class="nf"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;except&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And unless it is confined with arguments &lt;code&gt;only&lt;/code&gt; or &lt;code&gt;except&lt;/code&gt;, it will automatically define get, post, put, patch, and delete routes for your resource and route them to the following methods in the controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;destroy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that it is not currently possible to define a different behavior for HEAD and GET methods ont he same path, because if a GET is defined it will also automatically add the matching HEAD route. That will result in two HEAD routes existing for the same path and trigger error &lt;code&gt;Amber::Exceptions::DuplicateRouteError&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Views
&lt;/h1&gt;

&lt;p&gt;Information about views can be summarized in bullet points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Views in Amber are located in &lt;code&gt;src/views/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;They are rendered using &lt;code&gt;render()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The first argument given to &lt;code&gt;render()&lt;/code&gt; is the template name (e.g. &lt;code&gt;render("index.slang")&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If we are in the context of a controller, &lt;code&gt;render("index.slang")&lt;/code&gt; will look for view using the path &lt;code&gt;src/views/&amp;lt;controller_name&amp;gt;/index.slang&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If we are not rendering a partial, by default the template will be wrapped in a layout&lt;/li&gt;
&lt;li&gt;If the layout name isn't given, the default layout will be &lt;code&gt;views/layouts/application.slang&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;There is no unnecessary magic applied to template names — name given is the name that is looked up on disk&lt;/li&gt;
&lt;li&gt;Partials begin with "_" by convention, but that is not required&lt;/li&gt;
&lt;li&gt;To render a partial, use &lt;code&gt;render( partial: "_name.ext")&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Variables in Views
&lt;/h1&gt;

&lt;p&gt;In Amber, templates are compiled in the same scope as controller methods. This means you do not need instance variables for passing the information from controllers to views.&lt;/p&gt;

&lt;p&gt;Any variable you define in the controller method is automagically visible in the template. For example, let's add the current date and time display to our /about page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vi src/controllers/page_controller.cr

def about
    &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; Time.now
    render &lt;span class="s2"&gt;"about.ecr"&lt;/span&gt;
end

&lt;span class="nv"&gt;$ &lt;/span&gt;vi src/views/page/about.ecr

Hello, World! The &lt;span class="nb"&gt;time &lt;/span&gt;is now &amp;lt;%&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt; %&amp;gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Templates are actually executing in the controller class. If you do "&amp;lt;%= self.class %&amp;gt; in the above example, the response will be "PageController". So all the methods and variables you have on the controller are also available in views rendered from it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Starting the Server
&lt;/h1&gt;

&lt;p&gt;It is important to explain exactly what is happening from when you run the application til Amber starts serving the aplication:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;crystal src/&amp;lt;app_name&amp;gt;.cr&lt;/code&gt; - you or a script starts Amber

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;require "../config/*"&lt;/code&gt; - as the first thing, &lt;code&gt;config/*&lt;/code&gt; is required. Inclusion is in alphabetical order. Crystal only looks for *.cr files and only files in config/ are loaded (no subdirectories)

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;require "../config/application.cr"&lt;/code&gt; - this is usually the first file in &lt;code&gt;config/&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;require "./initializers/**"&lt;/code&gt; - loads all initializers. There is only one initializer file by default, named &lt;code&gt;initializer/database.cr&lt;/code&gt;. Here we have a double star ("**") meaning inclusion of all files including in subdirectories. Inclusion is always current-dir first, then depth&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;require "amber"&lt;/code&gt; - Amber itself is loaded

&lt;ol&gt;
&lt;li&gt;Loading Amber makes &lt;code&gt;Amber::Server&lt;/code&gt; class available&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;include Amber::Environment&lt;/code&gt; - already in this stage, environment is determined and settings are loaded from yml file (e.g. from &lt;code&gt;config/environments/development.yml&lt;/code&gt;. Settings are later available as &lt;code&gt;settings&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;require "../src/controllers/application_controller"&lt;/code&gt; - main controller is required. This is the base class for all other controllers

&lt;ol&gt;
&lt;li&gt;It defines &lt;code&gt;ApplicationController&lt;/code&gt;, includes JasperHelpers in it, and sets default layout ("application.slang").&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;require "../src/controllers/**"&lt;/code&gt; - all other controllers are loaded&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;Amber::Server.configure&lt;/code&gt; block is invoked to override any config settings&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;require "config/routes.cr"&lt;/code&gt; - this again invokes &lt;code&gt;Amber::Server.configure&lt;/code&gt; block, but concerns itself with routes and feeds all the routes in&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;Amber::Server.start&lt;/code&gt; is invoked

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;instance.run&lt;/code&gt; - implicitly creates a singleton instance of server, saves it to &lt;code&gt;@@instance&lt;/code&gt;, and calls &lt;code&gt;run&lt;/code&gt; on it&lt;/li&gt;
&lt;li&gt;Consults variable &lt;code&gt;settings.process_count&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If process count is 1, &lt;code&gt;instance.start&lt;/code&gt; is called&lt;/li&gt;
&lt;li&gt;If process count is &amp;gt; 1, the desired number of processes is forked, while main process enters sleep

&lt;ol&gt;
&lt;li&gt;Forks invoke Process.run() and start completely separate, individual processes which go through the same initialization procedure from the beginning. Forked processes have env variable "FORKED" set to "1", and a variable "id" set to their process number. IDs are assigned in reverse order (highest number == first forked).&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;instance.start&lt;/code&gt; is called for every process

&lt;ol&gt;
&lt;li&gt;It saves current time and prints startup info&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@handler.prepare_pipelines&lt;/code&gt; is called. @handler is Amber::Pipe::Pipeline, a subclass of Crystal's HTTP::Handler. &lt;code&gt;prepare_pipelines&lt;/code&gt; is called to connect the pipes so the processing can work, and implicitly adds Amber::Pipe::Controller (the pipe in which app's controller is invoked) as the last pipe. This pipe's duty is to call Amber::Router::Context.process_request, which actually dispatches the request to the controller.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;server = HTTP::Server.new(host, port, @handler)&lt;/code&gt;- Crystal's HTTP server is created&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server.tls = Amber::SSL.new(...).generate_tls if ssl_enabled?&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Signal::INT is trapped (calls &lt;code&gt;server.close&lt;/code&gt; when received)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loop do server.listen(settings.port_reuse) end&lt;/code&gt; - server enters main loop&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;/ol&gt;

&lt;h1&gt;
  
  
  Serving Requests
&lt;/h1&gt;

&lt;p&gt;Similarly as with starting the server, is important to explain exactly what is happening when Amber is serving requests:&lt;/p&gt;

&lt;p&gt;Amber's app serving model is based on Crystal's built-in, underlying functionality:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The server that is running is an instance of Crystal's
 &lt;a href="https://crystal-lang.org/api/0.24.1/HTTP/Server.html" rel="noopener noreferrer"&gt;HTTP::Server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;On every incoming request, handler is invoked. As supported by Crystal, handler can be simple Proc or an instance of HTTP::Handler. HTTP::Handlers have a concept of "next" and multiple ones can be connected in a row. In Amber, these individual handlers are called "pipes", and the overall handler (complete chain of pipes) is Amber::Pipe::Pipeline, which, for convenience, is also a subclass of &lt;a href="https://crystal-lang.org/api/0.24.1/HTTP/Handler.html" rel="noopener noreferrer"&gt;HTTP::Handler&lt;/a&gt;. This handler, even though named Pipeline and implying a specific pipeline, is actually aware of all pipelines and can identify and trigger the appropriate one&lt;/li&gt;
&lt;li&gt;In the pipeline, every Pipe (Amber::Pipe::*, ultimately subclass of Handler) is invoked, with one argument. That argument is
 by convention called "context" and it is an instance of &lt;code&gt;HTTP::Server::Context&lt;/code&gt;, which has two built-in methods — &lt;code&gt;request&lt;/code&gt; and &lt;code&gt;response&lt;/code&gt;, to access the request and response parts respectively. On top of that, Amber adds various other methods and variables, such as &lt;code&gt;router&lt;/code&gt;, &lt;code&gt;flash&lt;/code&gt;, &lt;code&gt;cookies&lt;/code&gt;, &lt;code&gt;session&lt;/code&gt;, &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;route&lt;/code&gt;, and others as seen in &lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/router/context.cr" rel="noopener noreferrer"&gt;src/amber/router/context.cr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Please note that calling the chain of pipes is not automatic; every pipe needs to call &lt;code&gt;call_next(context)&lt;/code&gt; at the appropriate point in its execution to call the next pipe in a row. It is not necessary to check whether the next pipe exists, because currently &lt;code&gt;Amber::Pipe::Controller&lt;/code&gt; is always implicitly added as the last pipe, so at least one does exist. State between pipes is not passed via variables but via modifying &lt;code&gt;context&lt;/code&gt; and the data contained in it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After that, pipelines, pipes, routes, and otherAmber-specific parts come into play.&lt;/p&gt;

&lt;p&gt;So, in detail, from the beginning:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;loop do server.listen(settings.port_reuse) end&lt;/code&gt; - main loop is running

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;spawn handle_client(server.accept?)&lt;/code&gt; - handle_client() is called in a new fiber after connection is accepted

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;io = OpenSSL::SSL::Socket::Server.new(io, tls, sync_close: true) if @tls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@processor.process(io, io)&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;if request.is_a?(HTTP::Request::BadRequest); response.respond_with_error("Bad Request", 400)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response.version = request.version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response.headers["Connection"] = "keep-alive" if request.keep_alive?&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;context = Context.new(request, response)&lt;/code&gt; - this context is already extended with Amber's extensions in &lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/router/context.cr" rel="noopener noreferrer"&gt;src/amber/router/context.cr&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@handler.call(context)&lt;/code&gt; - &lt;code&gt;Amber::Pipe::Pipeline.call()&lt;/code&gt; is called

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;raise ...error... if context.invalid_route?&lt;/code&gt; - route validity is checked early&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;if context.websocket?; context.process_websocket_request&lt;/code&gt; - if websocket, parse as such&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;elsif ...; ...pipeline.first...call(context)&lt;/code&gt; - if reqular HTTP request, call the first handler in the appropriate pipeline

&lt;ol&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;/ol&gt;

&lt;h1&gt;
  
  
  Static Pages
&lt;/h1&gt;

&lt;p&gt;It can be pretty much expected that a website will need a set of simple, "static" pages. Those pages are served by the application, but mostly don't use a database nor any complex code. Such pages might include About and Contact pages, Terms of Conditions, etc. Making this work is trivial.&lt;/p&gt;

&lt;p&gt;Let's say that, for simplicity and grouping, we want all "static" pages to be served by PageController. We will group all these pages under a common web-accessible prefix of /page/, and finally we will route page requests to PageController's methods. (Because these pages won't be objects, we won't need a model or anything else other than one controller method and one view per each page.)&lt;/p&gt;

&lt;p&gt;Let's start by creating a controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;amber g controller page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Afterwards, we edit &lt;code&gt;config/routes.cr&lt;/code&gt; to link URL "/about" to method about() in PageController. We do this inside the "routes :web" block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;routes :web do 
  ...
  get "/about", PageController, :about
  ...
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we edit the controller and actually add method about(). This method can just directly return some string in response, or it can render a view, and then the expanded view contents will be returned as the response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vi src/controllers/page_controller.cr

&lt;span class="c"&gt;# Inside the file, we add:&lt;/span&gt;

def about
  &lt;span class="c"&gt;# "return" can be omitted here. It is included only for clarity.&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;render &lt;span class="s2"&gt;"about.ecr"&lt;/span&gt;
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this is happening in the "page" controller, the view directory for finding the templates defaults to &lt;code&gt;src/views/page/&lt;/code&gt;. We will create the directory and the file "about.ecr" in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; src/views/page/
&lt;span class="nv"&gt;$ &lt;/span&gt;vi src/views/page/about.ecr

&lt;span class="c"&gt;# Inside the file, we add:&lt;/span&gt;

Hello, World!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because we have called render() without additional arguments, the template will default to being rendered within the default application layout, &lt;code&gt;views/layouts/application.cr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And that's it! Visiting &lt;code&gt;/about&lt;/code&gt; will go to the router, router will invoke &lt;code&gt;PageController::about()&lt;/code&gt;, that method will render template &lt;code&gt;src/views/page/about.ecr&lt;/code&gt; in the context of layout &lt;code&gt;views/layouts/application.cr&lt;/code&gt;, and the result of rendering will be a full page with content &lt;code&gt;Hello, World!&lt;/code&gt; in the body. That result will be returned to the controller, and from there it will be returned to the client.&lt;/p&gt;

&lt;h1&gt;
  
  
  Assets Pipeline
&lt;/h1&gt;

&lt;p&gt;In an Amber project, raw assets are in &lt;code&gt;src/assets/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;src/assets/
src/assets/fonts
src/assets/images
src/assets/javascripts
src/assets/javascripts/main.js
src/assets/stylesheets/main.scss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At build time, all these are processed and placed under &lt;code&gt;public/dist/&lt;/code&gt;.&lt;br&gt;
The JS resources are bundled to &lt;code&gt;main.bundle.js&lt;/code&gt; and CSS resources are bundled to &lt;code&gt;main.bundle.css&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Currently, webpack is being used for asset management. I recommend replacing it with at least &lt;a href="https://parceljs.org/" rel="noopener noreferrer"&gt;Parcel&lt;/a&gt;. Finding a non-js/non-node/non-npm application for this purpose would be even better; please let me know if you know one.&lt;/p&gt;

&lt;p&gt;This section will be expanded to include a full replacement procedure. (In general it seems it shouldn't be much more complex than replacing the command and development dependencies in project's &lt;code&gt;package.json&lt;/code&gt; file.)&lt;/p&gt;
&lt;h1&gt;
  
  
  Default Shards
&lt;/h1&gt;

&lt;p&gt;By default, Amber project depends on just a few shards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amberframework/amber          - Obviously, must depend on Amber
amberframework/granite-orm    - Database ORM
amberframework/quartz-mailer  - Sending and receiving emails
amberframework/jasper-helpers - Helpers for working with HTML in Amber/Crystal
will/crystal-pg               - PostgreSQL connector
amberframework/garnet-spec    - Extended Crystal specs for testing web applications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In turn, these depend on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;luislavena/radix                      - Radix Tree implementation
jeromegn/kilt                         - Generic template interface
jeromegn/slang                        - Slang template language
stefanwille/crystal-redis             - 
amberframework/cli                    - Building cmdline apps (based on mosop)
mosop/optarg                          - Parsing cmdline args
mosop/callback                        - Defining and invoking callbacks
mosop/string_inflection               - Word plurals, counts, etc.
amberframework/teeplate               - Rendering multiple template files
juanedi/micrate                       - Database migration tool
crystal-lang/crystal-db               - Common DB API
jwaldrip/shell-table.cr               - Creates textual tables in shell
askn/spinner                          - Spinner for the shell
crystal-lang/crystal-mysql            - 
crystal-lang/crystal-sqlite3          - 
amberframework/smtp.cr                - SMTP client (to be replaced with arcage/crystal-email)
ysbaddaden/selenium-webdriver-crystal - Selenium Webdriver client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And basic Crystal's build-in shards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http
logger
json
colorize
random/secure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the parts that are used end up in the compiled project.&lt;/p&gt;

&lt;p&gt;Now let's take a tour of all the important classes that exist in the Amber application and are useful for understanding the flow.&lt;/p&gt;

&lt;h1&gt;
  
  
  Extensions
&lt;/h1&gt;

&lt;p&gt;Amber adds some very convenient extensions to existing String and Number classes. The extensions are in the &lt;a href="https://github.com/amberframework/amber/tree/master/src/amber/extensions" rel="noopener noreferrer"&gt;extensions/&lt;/a&gt; directory, but here's a listing of the current ones:&lt;/p&gt;

&lt;p&gt;For String:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;str?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;email?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;domain?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;url?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ipv4?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ipv6?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mac_address?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hex_color?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hex?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;alpha?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;numeric?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;alphanum?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;md5?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;base64?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;slug?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lower?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upper?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;credit_card?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;phone?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;excludes?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;time_string?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;positive?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;negative?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;zero?&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;div?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;above?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;below?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lt?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;self?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lteq?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;between?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gteq?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Support Routines
&lt;/h1&gt;

&lt;p&gt;In &lt;a href="https://github.com/amberframework/amber/tree/master/src/amber/support" rel="noopener noreferrer"&gt;support/&lt;/a&gt; directory there is a number of various support files that provide additional, ready made routines.&lt;/p&gt;

&lt;p&gt;Currently, the following can be found there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client_reload.cr      - Support for reloading developer's browser

file_encryptor.cr     - Support for storing/reading encrypted versions of files
message_encryptor.cr
message_verifier.cr

locale_formats.cr     - Very basic locate data for various, manually-added locales

mime_types.cr         - List of MIME types and helper methods for working with them:
                        def self.mime_type(format, fallback = DEFAULT_MIME_TYPE)
                        def self.zip_types(path)
                        def self.format(accepts)
                        def self.default
                        def self.get_request_format(request)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Amber::Controller::Base
&lt;/h1&gt;

&lt;p&gt;This is the base controller from which all other controllers inherit. Source file is in &lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/controller/base.cr" rel="noopener noreferrer"&gt;src/amber/controller/base.cr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On every request, the appropriate controller is instantiated and its initialize() runs. Since this is the base controller, this code runs on every request so you can understand what is available in the context of every controller.&lt;/p&gt;

&lt;p&gt;The content of this controller and the methods it gets from including other modules are intuitive enough to be copied here and commented where necessary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Amber::Controller&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Base&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CSRF&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Redirect&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Render&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Responders&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Route&lt;/span&gt;
    &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Callbacks&lt;/span&gt;

    &lt;span class="kp"&gt;protected&lt;/span&gt; &lt;span class="kp"&gt;getter&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Context&lt;/span&gt;
    &lt;span class="kp"&gt;protected&lt;/span&gt; &lt;span class="kp"&gt;getter&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Amber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validators&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Params&lt;/span&gt;

    &lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="ss"&gt;:cookies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:flash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:requested_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:valve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:request_handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:websocket?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:get?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:post?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:patch?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;:put?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:delete?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:head?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:client_ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:halt!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@context&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Server&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="vi"&gt;@params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Amber&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Validators&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/csrf.cr" rel="noopener noreferrer"&gt;Helpers::CSRF&lt;/a&gt; provides these methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;csrf_token&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;csrf_tag&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;csrf_metatag&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/redirect.cr" rel="noopener noreferrer"&gt;Helpers::Redirect&lt;/a&gt; provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;redirect_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;redirect_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;redirect_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;controller&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Symbol&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="no"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;redirect_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/render.cr" rel="noopener noreferrer"&gt;Helpers::Render&lt;/a&gt; provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;    &lt;span class="no"&gt;LAYOUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application.slang"&lt;/span&gt;
    &lt;span class="k"&gt;macro&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"src/views"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/responders.cr" rel="noopener noreferrer"&gt;Helpers::Responders&lt;/a&gt; helps control what final status code, body, and content-type will be returned to the client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/route.cr" rel="noopener noreferrer"&gt;Helpers::Route&lt;/a&gt; provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action_name&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route_resource&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route_scope&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;controller_name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/amberframework/amber/blob/master/src/amber/dsl/callbacks.cr" rel="noopener noreferrer"&gt;Callbacks&lt;/a&gt; provide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight crystal"&gt;&lt;code&gt;    &lt;span class="k"&gt;macro&lt;/span&gt; &lt;span class="nf"&gt;before_action&lt;/span&gt;
    &lt;span class="k"&gt;macro&lt;/span&gt; &lt;span class="nf"&gt;after_action&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;We hope you have enjoyed this hands-on introduction to Amber!&lt;/p&gt;

&lt;p&gt;Feel free to provide any feedback on content or additional areas you&lt;br&gt;
would like to see covered in this guide. Thanks!&lt;/p&gt;

</description>
      <category>crystal</category>
      <category>amber</category>
      <category>framework</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Recipe — Building an Amber Authentication System</title>
      <dc:creator>Elias Perez</dc:creator>
      <pubDate>Sat, 13 Jan 2018 21:47:38 +0000</pubDate>
      <link>https://dev.to/eliasjpr/recipebuilding-an-amber-authentication-system-517</link>
      <guid>https://dev.to/eliasjpr/recipebuilding-an-amber-authentication-system-517</guid>
      <description>&lt;h1&gt;
  
  
  Recipe — Building an Amber Authentication System
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://amberframework.org" rel="noopener noreferrer"&gt;https://amberframework.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AiCbPqtqO3aW7vpobL9pMog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1600%2F1%2AiCbPqtqO3aW7vpobL9pMog.png" alt="amber framework example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Familiar with MVC and terms such as Migrations, Database&lt;/li&gt;
&lt;li&gt;Amber Framework CLI already installed (Installation Instructions)&lt;/li&gt;
&lt;li&gt;One of the supported databases PostgreSQL, MySQL, SQLite&lt;/li&gt;
&lt;li&gt;Terminal console&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Overview
&lt;/h1&gt;

&lt;p&gt;Virtually all web applications require a form of authenticating and registering users. As a result, most web frameworks have an excessive amount of options for implementing authentication, but Amber Framework differs by providing a built-in generator for basic authentication that can be customized and extended easily providing your tools that allows you to get started quick without unnecessary complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Amber Authentication Generator
&lt;/h2&gt;

&lt;p&gt;Before you start make sure you have all the prerequisites. Let’s start by first generating an application, for our example we will call our application Blogsy.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Generate the Blogsy App
&lt;/h4&gt;

&lt;p&gt;First, we generate our app by typing the following command in a terminal console.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;amber new blogsy --deps&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The --deps will automatically install project dependencies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  2. Go to your project directory
&lt;/h4&gt;

&lt;p&gt;Amber Commands are perform within the root directory of our project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cd blogsy&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Scaffold the User Authentication
&lt;/h4&gt;

&lt;p&gt;Next, scaffold the authentication system this will generate a list of files needed in order to enable authentication for our blog. FType the following command within the root directory of the Blogsy project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;amber g auth User&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The above command will give you the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;08:26:45 Generate   | Rendering Auth user
08:26:45 Generate   | new       config/initializers/granite.cr
08:26:45 Generate   | new       spec/models/user_spec.cr
08:26:45 Generate   | new       spec/models/spec_helper.cr
08:26:45 Generate   | new       spec/controllers/spec_helper.cr
08:26:45 Generate   | new       db/migrations/20180110202645523_create_user.sql
08:26:45 Generate   | new       db/seeds.cr
08:26:45 Generate   | new       src/models/user.cr
08:26:45 Generate   | new       src/controllers/registration_controller.cr
08:26:45 Generate   | new       src/controllers/user_controller.cr
08:26:45 Generate   | new       src/controllers/session_controller.cr
08:26:45 Generate   | new       src/views/registration/new.slang
08:26:45 Generate   | rewritten src/views/layouts/_nav.slang
08:26:45 Generate   | new       src/views/user/show.slang
08:26:45 Generate   | new       src/views/user/edit.slang
08:26:45 Generate   | new       src/views/session/new.slang
08:26:45 Generate   | new       src/handlers/authenticate.cr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To finalize installing the authentication system migrate the database with the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;amber db create migrat&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;08:52:25 (INFO) Database    | Created database blogsy_development
08:52:25 (INFO) Database    | Migrating db, current version: 0, target: 20180110205213147
08:52:25 (INFO) Database    | OK   20180110205213147_create_user.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can run our local development server and see our working authentication system.&lt;/p&gt;

&lt;p&gt;Run the following command &lt;code&gt;amber w&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;08:53:52 Watcher    | Terminating app Blogsy...
08:53:52 Watcher    | Compiling Blogsy...
08:53:52 Watcher    | Building project Blogsy...
08:54:05 (INFO) Server  | [Amber 0.6.1] serving application "Blogsy" at http://0.0.0.0:3000
08:54:05 (INFO) Server  | Server started in development.
08:54:05 (INFO) Server  | Startup Time 00:00:00.000596000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Sign up and Sign in Pages
&lt;/h4&gt;

&lt;p&gt;To see the signup page visit &lt;a href="http://0.0.0.0:3000/signup" rel="noopener noreferrer"&gt;http://0.0.0.0:3000/signup&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;Amber framework provides a built-in authentication generator that takes away complexity away, allowing the developer to focus on the task rather on implementations details. The built in authentication system is simple make it easy to modify to developer needs.&lt;/p&gt;

&lt;p&gt;Next&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- [From Zero to Deploy](https://amberframework.org/guides/getting-started/quick-start.md#zero-to-deploy)
- [Amber Framework Guides](https://amberframework.org/guides/getting-started/README.md#getting-started)
- [Crystal Play](https://play.crystal-lang.org/#/cr) — an online playground to run Crystal code
- Get real time help [Amber Community Channel](https://gitter.im/amberframework/amber)
- GitHub (help the community)[https://github.com/amberframework/amber] and star the project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>authentication</category>
      <category>webapp</category>
      <category>crystal</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Why you should try Amber Framework</title>
      <dc:creator>Elias Perez</dc:creator>
      <pubDate>Mon, 08 Jan 2018 05:46:37 +0000</pubDate>
      <link>https://dev.to/eliasjpr/why-you-should-try-amber-franework-100n</link>
      <guid>https://dev.to/eliasjpr/why-you-should-try-amber-franework-100n</guid>
      <description>

&lt;h1&gt;
  
  
  Amber Crystal Web Framework
&lt;/h1&gt;

&lt;p&gt;As a web developer we all have flirt with the idea of having a the perfect web framework, one that is fun to work, that does not get in your way, catches bugs early, easily to scale, deploy anywhere and adaptable to different architecture patterns without sacrificing performance and difficult to learn.&lt;/p&gt;

&lt;p&gt;Amber amberframework.org (&lt;a href="http://amberframework.org/"&gt;http://amberframework.org/&lt;/a&gt;) takes an aim to address these pain-points by leveraging on Crystal Language features and converging a series of widely accepted features.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Amber is a web application framework written in Crystal. Amber makes building web applications fast, simple, and enjoyable with blazing fast performance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Why Amber Framework?
&lt;/h3&gt;

&lt;p&gt;Amber is a web application framework written in Crystal. Amber makes building web applications fast, simple, and enjoyable with blazing fast performance. Converging concepts and features that have been widely accepted by developers.&lt;/p&gt;

&lt;p&gt;Here is why you should consider using Amber for your next project:&lt;/p&gt;

&lt;h4&gt;
  
  
  Crystal Language
&lt;/h4&gt;

&lt;p&gt;Crystal as a programming language offers, a high level syntax with the performance and characteristics of a low level language. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Syntax&lt;/strong&gt; - Write a program with highly expressive with fewer lines of code. Easy to read without scarifying performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type System&lt;/strong&gt; - Statically type checked, so any type errors will be caught early by the compiler rather than fail on runtime. Fewer bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Null Reference Checks&lt;/strong&gt; - All types are non-nilable in Crystal, and nilable variables are represented as a union between the type and nil. Again fewer bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Macros&lt;/strong&gt; - Crystal’s answer to metaprogramming is a powerful macro system, which ranges from basic templating and AST inspection. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency Model&lt;/strong&gt; - Crystal uses green threads, called fibers, to achieve concurrency. Fibers communicate with each other using channels, as in Go or Clojure, without having to turn to shared memory or locks. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Familiarity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amber is very easy to learn and pick up. With a friendly and easy to read with Ruby-like syntax,  Amber shares the same concepts as other popular web application frameworks that you find in other languages like Ruby, Express, Elixir, Laravel, making easy to understand and familiar to work with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amber is for programmers who want more performance, who enjoy working in a high-level scripting, combining native execution speed and concurrency so you will feel right at home, without losing your joy in programming because of code complexity.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;02:28:43 Request | Started 2018-01-07 14:28:43 - 05:00
02:28:43 Request | Status: 200  Method: GET Pipeline: web Format: html
02:28:43 Request | Requested Url: /
02:28:43 Request | Time Elapsed: 94.0µs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Convergence of features&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amber developers did not stop with Crystal features, Amber converges features widely accepted from a broad series of web frameworks, sharing a lot of similarity with other web application frameworks widely used today, specially with Ruby On Rails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Controller&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While not a new concept at all Amber does not re-invent the wheel by having this concept Amber keeps the developer in familiar grounds.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class UsersController &amp;lt; ApplicationController
  # Filters are methods that are run "before", "after" a controller action.
  before_action do
    # runs for specified actions
    only [:index, :world, :show] { increment(1) }
    # runs for all actions
    all { increment(1) }
  end

  after_action do
    # runs for specified actions
    only [:index, :world] { increment(1) }
  end

  def index
    @users = Users.all
    render("index.slang")
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Views Templates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amber supports a wide range of templating languages, there are 4 different templating languages supported: &lt;strong&gt;slang&lt;/strong&gt;, &lt;strong&gt;ecr&lt;/strong&gt;, &lt;strong&gt;mustache&lt;/strong&gt;, and &lt;strong&gt;temel&lt;/strong&gt;. Since Crystal is a compiled language all view template are precompiled, at runtime, all templates are already loaded into memory, there’s no disk reads, complex file caching, or template engine computation involved, allowing your pages to render fast and feel snappy.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doctype html
html
  head
    meta name="viewport" content="width=device-width,initial-scale=1.0"
    title This is a title
    css:
      h1 {color: red;} 
      p {color: green;}
    style h2 {color: blue;}
  body
    ul 
     - @users.each do |user|
       li = "#{user.name} - #{user.email}"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pipelines&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A pipeline is a series composable transformations throughout the connection life-cycle. We interact with pipes at every step of the connection life-cycle, and the core Amber components like Endpoints, Routers, and Controllers are all just Pipes internally. The basic idea of a Pipe is to unify the concept of a "connection" that we operate on. This differs from other HTTP middle-ware layers such as Rack, where the request and response are separated in the middle-ware stack.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Amber::Server.configure do |app|
  pipeline :web, :auth do
    plug Amber::Pipe::Logger.new
    plug Amber::Pipe::Flash.new
    plug Amber::Pipe::Session.new
    plug Amber::Pipe::CSRF.new
  end

  pipeline :auth do
    plug Authenticate.new
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Parameters Validation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the most important aspects of validation is to prevent the widespread of invalid data in programs. Parameters Validations is your first line of defense, we can set a series of rules for data correctness for a given endpoint this allows you to decouple endpoint validations from the model and invalidate the request early before executing more demanding operations.&lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update_params = params.validation do
  required(:name, "Your First Name is missing!") { |p| p.name? &amp;amp; !p.name.empty? }
  required(:email, "Your email address is invalid!") { |p| p.email? &amp;amp; p.size.between? 1..10 }
  required(:last_name) { |p| p.last_name? }
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Routing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amber defines routes explicitly in one central file for a couple of beneficial reasons. With the &lt;em&gt;&lt;em&gt;routes.cr&lt;/em&gt;&lt;/em&gt; file it is easy to understand at a high level which transformation are being performed for endpoints. Routes can be scoped to particular path, this is quite useful for representing API versions, CMS, Admin areas. Websocket routes are an exiting component, they allow to define full duplex communication channels over a single tcp connection with lower overhead, facilitating realtime data transfer form and to the server.  &lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;routes :web, "/pages" do
  get "/about", StaticController, :about
  resources "/posts", PostController
  websocket "/chat", ChatSocket
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Channels&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All web-socket messages are routed through channels, and channel topics are where clients subscribe to listen for new messages. Channels define 3 public methods that can be used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;handle_joined - Called when a user joins a channel.&lt;/li&gt;
&lt;li&gt;handle_message - Called when a user sends a message to a channel. A common message handler will simply rebroadcast the message to the other subscribers with rebroadcast! method.&lt;/li&gt;
&lt;li&gt;handle_leave - Called when a user leaves the channel.&lt;/li&gt;
&lt;/ul&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class HomeController &amp;lt; ApplicationController
  def index
    ChatSocket.broadcast("message", "chat_room:123", "message_new", {"message" =&amp;gt; "A new visitor!"})
    render("index.slang")
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ORM Agnostic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amber by default comes with a active record pattern orm called Granite::ORM. You can configure different ORM adapters to Amber since Amber models layers is not tied to Amber, you are free to use any of the Crystal available ORM Granite, Crecto, Jennifer and Lucky::Record&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amber Cli&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amber has a built in CLI tool, to make your life easier while developing applications.&lt;br&gt;
Here is a list of the available commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;d, deploy&lt;/strong&gt; - Provisions server and deploys project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;db, database&lt;/strong&gt; - Performs database maintenance tasks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;e, encrypt&lt;/strong&gt; - Encrypts environment YAML file. [env | -e --editor | --noedit]&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;x, exec&lt;/strong&gt; - Executes Crystal code within the application scope&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;g, generate&lt;/strong&gt; - Generate Amber classes (Controller, Migrations, Views, Models, Mailer, etc)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;n, new&lt;/strong&gt; - Generates a new Amber project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;routes&lt;/strong&gt; - Prints all defined application routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;w, watch&lt;/strong&gt; - Starts amber development server and rebuilds on file changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;System Tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amber was built with testing in mind.  Amber made it as simple as possible to have your system specs. Having a built in system test framework that runs consistently and faster than other frameworks. &lt;/p&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SomeFeature &amp;lt; GarnetSpec::System::Test
  scenario "user visits amber framework and sees getting started button" do
    visit "http://www.amberframework.org"
    timeout 1000
    click_on(:css, "header a.btn.btn-primary")
    wait 2000
    element(:tag_name, "body").text.should contain "Introduction"
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The Amber Framework team has work tirelessly on this project would like to encourage you to give Amber Framework a test run and see for yourself what value it brings to you as an engineer and to your organization.&lt;/p&gt;

&lt;p&gt;amberframework.org (&lt;a href="http://amberframework.org/"&gt;http://amberframework.org/&lt;/a&gt;) &lt;/p&gt;


</description>
      <category>amberframework</category>
      <category>learning</category>
      <category>crystal</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
