{"id":15713656,"url":"https://github.com/docelic/amber-introduction","last_synced_at":"2025-05-07T13:03:24.948Z","repository":{"id":66956508,"uuid":"117389013","full_name":"docelic/amber-introduction","owner":"docelic","description":"Introduction to the Amber web framework and its features","archived":false,"fork":false,"pushed_at":"2019-11-13T22:24:38.000Z","size":475,"stargazers_count":52,"open_issues_count":0,"forks_count":5,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-05-07T13:03:17.406Z","etag":null,"topics":["amber","amber-framework","crecto","crystal","crystal-lang","granite-orm","slang","web-framework"],"latest_commit_sha":null,"homepage":"","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/docelic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":"support/Makefile","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-14T00:19:54.000Z","updated_at":"2025-01-23T20:21:41.000Z","dependencies_parsed_at":"2023-02-28T02:31:21.228Z","dependency_job_id":null,"html_url":"https://github.com/docelic/amber-introduction","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/docelic%2Famber-introduction","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/docelic%2Famber-introduction/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/docelic%2Famber-introduction/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/docelic%2Famber-introduction/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/docelic","download_url":"https://codeload.github.com/docelic/amber-introduction/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252883227,"owners_count":21819158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["amber","amber-framework","crecto","crystal","crystal-lang","granite-orm","slang","web-framework"],"created_at":"2024-10-03T21:32:43.472Z","updated_at":"2025-05-07T13:03:24.920Z","avatar_url":"https://github.com/docelic.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/docelic/amber-introduction/master/support/amber.png\"\u003e\n  \u003ch3 align=\"center\"\u003e\u003cstrong\u003eIntroduction to the Amber Web Framework\u003c/strong\u003e\u003cbr\u003e\n  And its Out-of-the-Box Features\u003c/h3\u003e\n  \u003cp align=\"center\"\u003e\n    \u003csup\u003e\n      \u003ci\u003e\n        Amber makes building web applications easy, fast, and enjoyable.\n      \u003c/i\u003e\n    \u003c/sup\u003e\n  \u003c/p\u003e\n  \u003cp align=\"center\"\u003e\n  \u003c/p\u003e\n\u003c/p\u003e\n\n# Table of Contents\n\n1. [Introduction](#introduction)\n1. [Installation](#installation)\n1. [Creating New Amber App](#creating_new_amber_app)\n1. [Running the App](#running_the_app)\n1. [Building the App and Build Troubleshooting](#building_the_app_and_build_troubleshooting)\n1. [REPL](#repl)\n1. [File Structure](#file_structure)\n1. [Database Commands](#database_commands)\n1. [Pipes and Pipelines](#pipes_and_pipelines)\n1. [Routes, Controller Methods, and Responses](#routes__controller_methods__and_responses)\n1. [Views](#views)\n\t1. [Template Languages](#template_languages)\n\t\t1. [Liquid Template Language](#liquid_template_language)\n1. [Logging](#logging)\n1. [Parameter Validation](#parameter_validation)\n1. [Static Pages](#static_pages)\n1. [Variables in Views](#variables_in_views)\n1. [More on Database Commands](#more_on_database_commands)\n\t1. [Micrate](#micrate)\n\t1. [Custom Migrations Engine](#custom_migrations_engine)\n1. [Internationalization (I18n)](#internationalization__i18n_)\n1. [Responses](#responses)\n\t1. [Responses with Different Content-Type](#responses_with_different_content_type)\n\t1. [Error Responses](#error_responses)\n\t\t1. [Manual Error Responses](#manual_error_responses)\n\t\t1. [Error Responses via Error Pipe](#error_responses_via_error_pipe)\n1. [Assets Pipeline](#assets_pipeline)\n\t1. [Adding jQuery and jQuery UI](#adding_jquery_and_jquery_ui)\n\t1. [Resource Aliases](#resource_aliases)\n\t1. [CSS Optimization / Minification](#css_optimization___minification)\n\t1. [File Copying](#file_copying)\n\t1. [Asset Management Alternatives](#asset_management_alternatives)\n1. [Advanced Topics](#advanced_topics)\n\t1. [Amber::Controller::Base](#amber__controller__base)\n\t1. [Extensions](#extensions)\n\t1. [Shards](#shards)\n\t1. [Environments](#environments)\n\t1. [Starting the Server](#starting_the_server)\n\t1. [Serving Requests](#serving_requests)\n\t1. [Support Routines](#support_routines)\n\t1. [Amber behind a Load Balancer | Reverse Proxy | ADC](#amber_behind_a_load_balancer___reverse_proxy___adc)\n\n\n# Introduction\u003ca name=\"introduction\"\u003e\u003c/a\u003e\n\n**Amber** is a web application framework written in [Crystal](http://www.crystal-lang.org). Homepage can be found at [amberframework.org](https://amberframework.org/), docs at [Amber Docs](https://docs.amberframework.org), GitHub repository at [amberframework/amber](https://github.com/amberframework/amber), and the chat on [Gitter](https://gitter.im/amberframework/amber) or on the FreeNode IRC channel #amber.\n\nAmber 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 many concepts from Rails that are good.)\n\nThis 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 [Crystal and its features](https://crystal-lang.org/docs/overview/).\n\n# Installation\u003ca name=\"installation\"\u003e\u003c/a\u003e\n\n```shell\ngit clone https://github.com/amberframework/amber\ncd amber\nmake # The result of 'make' will be one file -- command line tool bin/amber\n\n# To install the file, or to symlink the system-wide executable to current directory, run one of:\nmake install # default PREFIX is /usr/local\nmake install PREFIX=/usr/local/stow/amber\nmake force_link # can also specify PREFIX=...\n```\n\n(\"stow\" mentioned above is referring to [GNU Stow](https://www.gnu.org/software/stow/).)\n\nAfter installation or linking, `amber` is the command you will be using for creating and managing Amber apps.\n\nPlease note that some users prefer (or must use for compatibility reasons) local Amber executables which match the version of Amber used in their project. For that, each Amber project's `shard.yml` ships with the build target named \"amber\":\n\n```\ntargets:\n  ...\n  amber:\n    main: lib/amber/src/amber/cli.cr\n\n```\n\nThanks to it, running `shards build amber` will compile local Amber found in `lib/amber/` and place the executable into the project's local file `bin/amber`.\n\n# Creating New Amber App\u003ca name=\"creating_new_amber_app\"\u003e\u003c/a\u003e\n\n```shell\namber new \u003capp_name\u003e [-d DATABASE] [-t TEMPLATE_LANG] [-m ORM_MODEL] [--deps]\n```\n\nSupported databases are [PostgreSQL](https://www.postgresql.org/) (pg, default), [MySQL](https://www.mysql.com/) (mysql), and [SQLite](https://sqlite.org/) (sqlite).\n\nSupported template languages are [slang](https://github.com/jeromegn/slang) (default) and [ecr](https://crystal-lang.org/api/0.21.1/ECR.html). (But any languages can be used; more on that can be found below in [Template Languages](#template_languages).)\n\nSlang is extremely elegant, but very different from the traditional perception of HTML.\nECR is HTML-like, very similar to Ruby ERB, and also much less efficient than slang, but it may be the best choice for your application if you intend to use some HTML site template (e.g. from [themeforest](https://themeforest.net/)) whose pages are in HTML + CSS or SCSS. (Or you could also try [html2slang](https://github.com/docelic/html2slang/) which converts the bulk of HTML pages into slang with relatively good accuracy.)\n\nSupported ORM models are [granite](https://github.com/amberframework/granite-orm) (default) and [crecto](https://github.com/Crecto/crecto).\n\nGranite is Amber's native, nice, and effective ORM model where you mostly write your own SQL. For example, 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.\n\nSupported migrations engine is [micrate](https://github.com/amberframework/micrate). (But any migrations engines can be used; more on that can be found below in [Custom Migrations Engine](#custom_migrations_engine).)\n\nMicrate is very simple and you basically write raw SQL in your migrations. There are just two keywords in the migration files which give instructions whether the SQLs that follow pertain to migrating up or down. These keywords are \"-- +micrate Up\" and \"-- +micrate Down\". If you have complex SQL statements that contain semicolons then you also enclose each in \"-- +micrate StatementBegin\" and \"-- +micrate StatementEnd\".\n\nFinally, if argument `--deps` is provided, Amber will automatically run `shards` in the new project's directory after creation to download the shards required by the project.\n\nPlease note that shards-related commands use the directory `.shards/` as local staging area before the contents are fully ready to replace shards in `lib/`.\n\n# Running the App\u003ca name=\"running_the_app\"\u003e\u003c/a\u003e\n\nBefore building or running Amber applications, you should install the following system packages: `libevent-dev libgc-dev libxml2-dev libssl-dev libyaml-dev libcrypto++-dev libsqlite3-dev`. These packages will make sure that you do not run into missing header files as soon as you try to run the application.\n\nOther than that, the app can be started as soon as you have created it and ran `shards` in the app directory.\n(It is not necessary to run `shards` if you have invoked `amber new` with the argument `--deps`; in that case Amber did it for you.)\n\nPlease note that the application is always compiled, regardless of whether one is using the Crystal command 'run' (the default) or 'build'. It is just that in run mode, the resulting binary is typically compiled without optimizations (to improve build speed) and is not saved to a file, but is compiled, executed, and then discarded.\n\nTo run the app, you could use a couple different approaches:\n\n```shell\n# For development, clean and simple - compiles and runs your app:\ncrystal src/\u003capp_name\u003e.cr\n\n# Compiles and runs app in 'production' environment:\nAMBER_ENV=production crystal src/\u003capp_name\u003e.cr\n\n# For development, clean and simple - compiles and runs your app, but\n# also watches for changes in files and rebuilds/re-runs automatically:\namber watch\n```\n\nAmber apps by default use a feature called \"port reuse\" available in newer Linux kernels. If you get an error \"setsockopt: Protocol not available\" upon running the app, it means your kernel does not support it. Please edit `config/environments/development.yml` and set \"port_reuse\" to false.\n\n# Building the App and Build Troubleshooting\u003ca name=\"building_the_app_and_build_troubleshooting\"\u003e\u003c/a\u003e\n\nTo build the application in a simple and effective way, you would run the following to produce executable file `bin/\u003capp_name\u003e`:\n\n```shell\n# For production, compiles app with optimizations and places it in bin/\u003capp_name\u003e.\nshards build \u003capp_name\u003e --production\n```\n\nTo build the application in a more manual way, skip dependency checking, and control more of the options, you would run:\n\n```shell\n# For production, compiles app with optimizations and places it in bin/\u003capp_name\u003e.\n# Crystal by default compiles using 8 threads (tune if needed with --threads NUM)\ncrystal build --no-debug --release --verbose -t -s -p -o bin/\u003capp_name\u003e src/\u003capp_name\u003e.cr\n```\n\nAs mentioned, 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.\n\nThanks to Crystal's compiler implementation, only the parts actually used are added to the executable. Listing dependencies in `shard.yml` or even using `require`s in your program will generally not affect what is compiled in.\n\nCrystal caches partial results of the compilation (*.o files etc.) under `~/.cache/crystal/` for faster subsequent builds. This directory is also where temporary binaries are placed when one runs programs with `crystal [run]` rather than `crystal build`.\n\nSometimes 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.\n\nThe 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 and solution will be determined easily. Usually some library or header files will be missing, such as those mentioned above in [Running the App](#running_the_app).\n\nThere are some issues with the `libgc` library here and there. Sometimes it helps to reinstall the system's package `libgc-dev`.\n\n# REPL\u003ca name=\"repl\"\u003e\u003c/a\u003e\n\nOften 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 `rails console`.\n\nCrystal does not have a free-form [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop), but you can save and execute scripts in the context of the application. One way to do it is via command `amber x [filename]`. This command will allow you to type or edit the contents, and then execute the script.\n\nAnother, possibly more flexible way to do it is via standalone REPL-like tools [cry](https://github.com/elorest/cry) or [icr](https://github.com/crystal-community/icr). `cry` began as an experiment and a predecessor to `amber x`, but now offers additional functionality such as repeatedly editing and running the script if `cry -r` is invoked.\n\nIn any case, running a script \"in application context\" simply means requiring `config/application.cr` (and through it, `config/**`). Therefore, be sure to list all your requires in `config/application.cr` so that everything works as expected, and if you are using `cry` or `icr`, have `require \"./config/application\"` as the first line.\n\n# File Structure\u003ca name=\"file_structure\"\u003e\u003c/a\u003e\n\nSo, at this point you might be wanting to know what's placed where in an Amber application. The default structure looks like this:\n\n```\n./config/                  - All configuration, detailed in subsequent lines:\n./config/initializers/     - Initializers (code you want executed at the very beginning)\n./config/environments/     - Environment-specific YAML configurations (development, production, test)\n./config/application.cr    - Main configuration file for the app. Generally not touched (apart\n                             from adding \"require\"s to the top) because most of the config\n                             settings are specified in YAML files in config/environments/\n./config/webpack/          - Webpack (asset bundler) configuration\n./config/routes.cr         - All routes\n\n./db/migrations/           - All DB migration files (created with \"amber g migration ...\")\n\n./public/                  - The \"public\" directory for static files\n./public/dist/             - Directory inside \"public\" for generated files and bundles\n./public/dist/images/\n\n./src/                     - Main source directory, with \u003capp_name\u003e.cr being the main file\n./src/controllers/         - All controllers\n./src/models/              - All models\n./src/views/layouts/       - All layouts\n./src/views/               - All views\n./src/views/home/          - Views for HomeController (the app's \"/\" path)\n./src/locales/             - Toplevel directory for locale (translation) files named [lang].yml\n./src/assets/              - Static assets which will be bundled and placed into ./public/dist/\n./src/assets/fonts/\n./src/assets/images/\n./src/assets/javascripts/\n./src/assets/stylesheets/\n\n./spec/                    - Toplevel directory for test files named \"*_spec.cr\"\n```\n\nI prefer to have some of these directories accessible directly in the root directory of the application and to have the config directory aliased to `etc`, so I run:\n\n```\nln -sf config etc\nln -sf src/assets\nln -sf src/controllers\nln -sf src/models\nln -sf src/views\nln -sf src/views/layouts\n\n```\n\n# Database Commands\u003ca name=\"database_commands\"\u003e\u003c/a\u003e\n\nAmber provides a group of subcommands under `amber db` to allow working with the database. The simple commands you will most probably want to run first just to see things working are:\n\n```shell\namber db create\namber db status\namber db version\n```\n\nBefore these commands will work, you will need to configure database\ncredentials as follows:\n\nFirst, create a user to access the database. For PostgreSQL, this is done by invoking something like:\n\n```shell\n$ sudo su - postgres\n$ createuser -dElPRS myuser\nEnter password for new role:\nEnter it again:\n```\n\nThen, edit `config/environments/development.yml` and configure \"database_url:\" to match your settings. If nothing else, the part that says \"postgres:@\" should be replaced with \"yourusername:yourpassword@\".\n\nAnd then try the database commands from the beginning of this section.\n\nPlease note that for the database connection to succeed, everything must be set up correctly \u0026mdash; hostname, port, username, password, and database name must be valid, 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 *any error in any of these requirements*, the error message will be terse and just say \"Connection unsuccessful: \u003cdatabase_url\u003e\". 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.\n\n(If you are sure that the username and password are correct and that the database server is accessible, then the most common problem is that the database does not exist yet, so you should run `amber db create` as the first command to create it.)\n\nPlease 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 `amber encrypt`.\n\n# Pipes and Pipelines\u003ca name=\"pipes_and_pipelines\"\u003e\u003c/a\u003e\n\nIn very simple frameworks it could suffice to directly map incoming requests to methods in the application, call them, and return their output to the user.\n\nMore elaborate application frameworks like Amber provide many more features and flexibility, and allow pluggable components to be inserted and executed in the chosen order before the actual controller method is invoked to handle the request.\n\nThese components are in general terminology called \"middleware\". Crystal calls them \"handlers\", and Amber calls them \"pipes\". In any case, in Amber applications they all refer to the same thing \u0026mdash; classes that `include` Crystal's module [HTTP::Handler](https://crystal-lang.org/api/0.24.2/HTTP/Handler.html) and that implement method `def call(context)`. (So in Amber, this functionality is based on Crystal's HTTP server's built-in support for handlers/pipes.)\n\nPipes work in such a way that invoking the pipes is not automatic, but each pipe must explicitly invoke `call_next(context)` to call the next pipe in a row. This is actually desirable because it makes it possible to call the next pipe at exactly the right place in the code where you want it and if you want it \u0026mdash; at the beginning, in the middle, or at the end of your current pipe's code, or not at all.\n\nThe request and response data that pipes need in order to run and do anything meaningful is passed as the first argument to every pipe, and is by convention named \"context\".\n\nContext persists for the duration of the request and is the place where data that should be shared/carried between pipes should be saved. Amber extends the default [HTTP::Server::Context](https://crystal-lang.org/api/0.24.2/HTTP/Server/Context.html) class with many additional fields and methods as can be seen in [router/context.cr](https://github.com/amberframework/amber/blob/master/src/amber/router/context.cr) and [extensions/http.cr](https://github.com/amberframework/amber/blob/master/src/amber/extensions/http.cr).\n\nHandlers or pipes are not limited in what they can do. It is normal that they sometimes stop execution and return an error, or fulfil the request on their own without even passing the request through to the controller. Examples of such pipes are [CSRF](https://github.com/amberframework/amber/blob/master/src/amber/pipes/csrf.cr) which stops execution if CSRF token is incorrect, or [Static](https://github.com/amberframework/amber/blob/master/src/amber/pipes/static.cr) which autonomously handles delivery of static files.\n\nUsing pipes promotes code reuse and is a nice way to plug various standard or custom functionality in the request serving process without requiring developers to duplicate code or include certain parts of code in every controller action.\n\nAdditionally, in Amber there exists a concept of \"pipelines\". Pipelines are logical groups of pipes. The discussion about them continues in the next section.\n\n# Routes, Controller Methods, and Responses\u003ca name=\"routes__controller_methods__and_responses\"\u003e\u003c/a\u003e\n\nBefore expanding the information on pipes and pipelines, let's explain the concept of routes.\n\nRoutes connect incoming requests (HTTP methods and URL paths) to specific controllers and controller methods in your application. Routes are checked in the order they are defined and the first route that matches wins.\n\nAll routes belong to a certain pipeline (like \"web\", \"api\", or similar). When a route matches, Amber simply executes all pipes in the pipeline under which that route has been defined. The last pipe in every pipeline is implicitly the pipe named \"[Controller](https://github.com/amberframework/amber/blob/master/src/amber/pipes/controller.cr)\". That's the pipe which actually looks into the original route, instantiates the specified controller, and calls the specified method in it. Please note that this is currently non-configurable \u0026mdash; the controller pipe is always automatically added as the last pipe in the pipeline and it is executed unless processing stops in one of the earlier pipes.\n\nThe configuration for pipes, pipelines, and routes is found in the file `config/routes.cr`. This file invokes the same `configure` block that `config/application.cr` does, but since routes configuration is important and can also be lengthy and complex, Amber keeps it in a separate file.\n\nAmber includes commands `amber routes` and `amber pipelines` to display route and pipeline configurations. By default, the output for routes looks like the following:\n\n```shell\n$ amber routes\n\n\n╔══════╦═══════════════════════════╦════════╦══════════╦═══════╦═════════════╗\n║ Verb | Controller                | Action | Pipeline | Scope | URI Pattern ║\n╠──────┼───────────────────────────┼────────┼──────────┼───────┼─────────────╣\n║ get  | HomeController            | index  | web      |       | /           ║\n╠──────┼───────────────────────────┼────────┼──────────┼───────┼─────────────╣\n║ get  | Amber::Controller::Static | index  | static   |       | /*          ║\n╚══════╩═══════════════════════════╩════════╩══════════╩═══════╩═════════════╝\n\n\n```\n\nFrom the first line of the output we see that a \"GET /\" request will cause all pipes in the pipeline \"web\" to be executed, and then\n`HomeController.new.index` method will be called.\n\nIn the `config/routes.cr` code, this is simply achieved with the line:\n\n```crystal\nroutes :web do\n  get \"/\", HomeController, :index\nend\n```\n\nThe return value of the controller method is returned as response body to the client.\n\nAs another example, the following definition would cause a POST request to \"/registration\" to result in invoking `RegistrationController.new.create`:\n\n```\npost \"/registration\", RegistrationController, :create\n```\n\nBy convention, standard HTTP verbs (GET/HEAD, POST, PUT/PATCH, and DELETE) should be routed to standard-named methods on the controllers \u0026mdash; `show`, `create`, `update`, and `destroy`. However, there is nothing preventing you from routing URLs to any methods you want in the controllers, such as we've seen with `index` above.\n\nWebsocket routes are supported too.\n\nThe DSL language specific to `config/routes.cr` file is defined in [dsl/router.cr](https://github.com/amberframework/amber/blob/master/src/amber/dsl/router.cr) and [dsl/server.cr](https://github.com/amberframework/amber/blob/master/src/amber/dsl/server.cr).\n\nIt gives you the following top-level commands/blocks:\n\n```\n# Define a pipeline\npipeline :name do\n  # ... list of pipes ...\nend\n\n# Group a set of routes\nroutes :pipeline_name, \"/optional_path_prefix\" do\n  # ... list of routes ...\nend\n```\n\nThis is used in practice in the following way in `config/routes.cr`:\n\n```crystal\nAmber::Server.configure do |app|\n  pipeline :web do\n    # Plug is the method used to connect a pipe (middleware).\n    # A plug accepts an instance of HTTP::Handler.\n    plug Amber::Pipe::Logger.new\n  end\n\n  routes :web do\n    get \"/\", HomeController, :index     # Routes \"GET /\" to HomeController.new.index\n    post \"/test\", PageController, :test # Routes \"POST /test\" to PageController.new.test\n  end\nend\n```\n\nWithin \"routes\" blocks the following commands are available:\n\n```crystal\nget, post, put (or patch), delete, options, head, trace, connect, websocket, resources\n```\n\nMost of these actions correspond to the respective HTTP methods; `websocket` defines websocket routes; and `resources` is a macro defined as:\n\n```crystal\n    macro resources(resource, controller, only = nil, except = nil)\n```\n\nUnless `resources` is confined with arguments `only` or `except`, it will automatically route `get`, `post`, `put/patch`, and `delete` HTTP methods to methods `index`, `show`, `new`, `edit`, `create`, `update`, and `destroy` on the controller.\n\nPlease note that it is not currently possible to define a different behavior for GET and HEAD HTTP methods on the same path. If a GET is defined, it will also automatically add the matching HEAD route. Specifying HEAD route manually would then result in two HEAD routes existing for the same path and trigger `Amber::Exceptions::DuplicateRouteError`.\n\n# Views\u003ca name=\"views\"\u003e\u003c/a\u003e\n\nInformation about views can be summarized in the following bullet points:\n\n- Views in an Amber project are located under the toplevel directory `src/views/`\n- Views are typically rendered using `render()`\n- The first argument given to `render()` is the template name (e.g. `render(\"index.slang\")`)\n- `render(\"index.slang\")` will look for a view named `src/views/\u003ccontroller_name\u003e/index.slang`\n- `render(\"./abs/or/rel/path.slang\")` will look for a template in that specific path\n- There is no unnecessary magic applied to template names \u0026mdash; names specified are the names that will be looked up on disk\n- If you are not rendering a partial, by default the template will be wrapped in a layout\n- If the layout name isn't specified, the default layout will be `views/layouts/application.slang`\n- To render a partial, use `render( partial: \"_name.ext\")`\n- Partials begin with \"\\_\" by convention, but that is not required. If they are named with \"\\_\", then the \"\\_\" must be mentioned as part of the name\n- Templates are read from disk and compiled into the application at compile time. This makes them fast to access and also read-only which is a useful side-benefit\n\nThe `render` macro is usually invoked at the end of the controller method. This makes its return value be the return value of the controller method as a whole, and as already mentioned, the controller method's return value is returned to the client as response body.\n\nIt is also important to know that `render` is a macro and that views are rendered directly (in-place) as part of the controller method.\nThis results in a very interesting property \u0026mdash; since `render` executes directly in the controller method, it sees all local variables in it and view data does not have to be passed via instance variables. This particular aspect is explained in more detail further below under [Variables in Views](#variables_in_views).\n\n## Template Languages\u003ca name=\"template_languages\"\u003e\u003c/a\u003e\n\nIn the introduction we've mentioned that Amber supports two template languages \u0026mdash; [slang](https://github.com/jeromegn/slang) (default) and [ecr](https://crystal-lang.org/api/0.21.1/ECR.html).\n\nThat's because Amber ships with a minimal working layout (a total of 3 files) in those languages, but there is nothing preventing you from using any other languages if you have your own templates or want to convert existing ones.\n\nAmber's default rendering engine is [Kilt](https://github.com/jeromegn/kilt), so all languages supported by Kilt should be usable out of the box. Amber does not make assumptions about the template language used; the view file's extension will determine which parser will be invoked (e.g. \".ecr\" for ecr, \".slang\" for slang).\n\n### Liquid Template Language\u003ca name=\"liquid_template_language\"\u003e\u003c/a\u003e\n\nThe original [Kilt](https://github.com/jeromegn/kilt) repository now has support for the Liquid template language.\n\nPlease note, however, that Liquid as a template language comes with non-typical requirements \u0026mdash; primarily, it requires a separate store (\"context\") for user data which is to be available in templates, and also it does not allow arbitrary functions, objects, object methods, and data types to be used in its templates.\n\nAs such, Amber's principle of rendering the templates directly inside controller methods (and thus making all local variables automatically available in views) cannot be used because Liquid's context is separate and local variables are not there.\n\nAlso, Liquid's implementation by default tries to be helpful and it automatically creates a new context. It copies all instance variables (@ivars) from the current object into the newly created context, which again cannot be used with Amber for two reasons.\nFirst, because the copying does not work for data other than basic types (e.g. saying `@process = Process` does not make `{{ process.pid }}` usable in a Liquid template). Second, because Amber's controllers already contain various instance variables that should not or can not be serialized, so even simply saying `render(\"index.liquid\")` would result in a compile-time error in Crystal even if the template itself was empty.\n\nAlso, Amber's `render` macro does not accept extra arguments, so a custom context can't be passed to Kilt and from there to Liquid.\n\nTherefore, the best approach to work with Liquid in Amber is to create a custom context, populate it with desired values, and then invoke `Kilt.render` macro directly (without using Amber's `render` macro). The pull request [#610](https://github.com/amberframework/amber/pull/610) to make rendering engines includable/choosable at will was refused by the Amber project, so if you are bothered that the default `render` macro is present in your application even though you do not use it, simply comment the line `include Helpers::Render` in Amber's [controller/base.cr](https://github.com/amberframework/amber/blob/master/src/amber/controller/base.cr).\n\nPlease also keep in mind not to use the name \"context\" for the variable that will hold Liquid's context, because that would take precedence over the `context` getter that already exists on the controllers and is used to access `HTTP::Server::Context` object.\n\nSo, altogether, a working example for rendering Liquid templates in Amber would look like the following (showing the complete controller code for clarity):\n\n```\nclass HomeController \u003c ApplicationController\n  def index\n    ctx = Liquid::Context.new\n    ctx.set \"process\", { \"pid\" =\u003e Process.pid }\n\n    # The following would render src/views/[controller]/index.liquid\n    Kilt.render \"index.liquid\", ctx\n\n    # The following would render specified path, relative to app base directory\n    Kilt.render \"src/views/myview.liquid\", ctx\n  end\nend\n```\n\n# Logging\u003ca name=\"logging\"\u003e\u003c/a\u003e\n\nAmber logger (based on standard Crystal's class `Logger`) is initialized as soon as `require \"amber\"` is called, as part of reading the settings and initializing the environment.\n\nThe variable containing the logger is `Amber.settings.logger`. For convenience, it is also available as `Amber.logger`. In the context of a Controller, it is also available as simply `logger`.\n\nControllers and views execute in the same class (the class of the controller), so calling the following anywhere in the controller or views will produce the expected log line:\n\n```crystal\nlogger.info \"Informational Message\"\n```\n\nLog levels available are `debug`, `info`, `warn`, `error`, `fatal`, and `unknown`.\n\nThe second, optional parameter passed to the log method will affect the displayed name of the subsystem from which the message originated. For example:\n\n\n```crystal\nlogger.warn \"Starting up\", \"MySystem\"\n```\n\nwould result in the log line:\n\n```\n03:17:04 MySystem   | (WARN) Starting up\n```\n\nIn you still need a customized logger for special cases or purposes, please create a separate `Logger.new` yourself.\n\n# Parameter Validation\u003ca name=\"parameter_validation\"\u003e\u003c/a\u003e\n\nFirst of all, Amber framework considers query and body params equal and makes them available to the application in the same, uniform way.\n\nSecond of all, the params handling in Amber is not programmed in a truly clean and non-overlapping way, but the description here should be clear to understand.\n\nThere are just three important methods to have in mind \u0026mdash; `params.validation {...}` which defines validation rules, `params.valid?` which returns whether parameters pass validation, and `params.validate!` which requires that parameters pass validation or raises an error.\n\nA simple validation process in a controller could look like this (showing the whole Controller class for completeness):\n\n```crystal\nclass HomeController \u003c ApplicationController\n  def index\n    params.validation do\n      required(:name) { |n| n.size \u003e 6 } # Name must have at least 6 characters\n      optional(:phone) { |n| n.phone? }  # Phone must look like a phone number\n    end\n\n    \"Params valid: #{params.valid?.to_s}\u003cbr\u003eName is: #{params[:name]}\"\n  end\nend\n```\n\n(Extensions to the String class such as `phone?` seen above come especially handy for writing validations. Please see [Extensions](#extensions) below for the complete list of built-in extensions available.)\n\nWith this foundation in place, let's take a step back to explain the underlying principles and also expand the full description:\n\nAs already mentioned above, for every incoming request, Amber uses data from `config/routes.cr` to determine which controller and method in it should handle the request. Then it instantiates that controller (calls `new` on it), and because all controllers inherit from `ApplicationController` (which in turn inherits from `Amber::Controller:Base`), the following code is executed as part of initialize:\n\n```crystal\nprotected getter params : Amber::Validators::Params\n\ndef initialize(@context : HTTP::Server::Context)\n  @params = Amber::Validators::Params.new(context.params)\nend\n```\n\nIn other words, `params` object is initialized using raw params passed with the user's request (i.e. `context.params`). From there, it is important to know that `params` object contains 4 important variables (getters):\n\n1. `params.raw_params` - this is a reference to hash `context.params` created during initialize, and all methods invoked on `params` directly (such as `[]`, `[]?`, `[]=`, `add`, `delete`, `each`, `fetch`, etc.) are forwarded to this object. Please note that this is a reference and not a copy, so all modifications made there also affect `context.params`\n1. `params.rules` - this is initially an empty list of validation rules. It is filled in as validation rules are defined using `params.validation {...}`\n1. `params.params` - this is a hash of key=value parameters, but only those that were mentioned in the validation rules and that passed validation when `valid?` or `validate!` were called. This list is re-initialized on every call to `valid?` or `validate!`. Using this hash ensures that you only work with validated/valid parameters\n1. `params.errors` - this is a list of all eventual errors that have ocurred during validation with `valid?` or `validate!`. This list is re-initialized on every call to `valid?` or `validate!`\n\nAnd this is basically all there is to it. From here you should have a complete understanding how to work with params validation in Amber.\n\n(TODO: Add info on model validations)\n\n# Static Pages\u003ca name=\"static_pages\"\u003e\u003c/a\u003e\n\nIt 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 amd Conditions, and so on. Making this work is trivial and will serve as a great example.\n\nLet's say that, for simplicity and logical grouping, we want all \"static\" pages to be served by a controller we will create, named \"PageController\". We will group all these \"static\" 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 backed by objects, we won't need models or anything else other than one controller method and one view per each page.\n\nLet's create the controller:\n\n```shell\namber g controller page\n```\n\nThen, we edit `config/routes.cr` to link e.g. URL \"/page/about\" to method about() in PageController. We do this inside the \"routes :web\" block:\n\n```\nroutes :web, \"/page\" do\n  ...\n  get \"/about\", PageController, :about\n  ...\nend\n```\n\nThen, we edit the controller and actually add method about(). This method can just directly return a string in response, or it can render a view. In any case, the return value from the method will be returned as the response body to the client, as usual.\n\n```shell\n$ vi src/controllers/page_controller.cr\n\n# Inside the file, we add:\n\ndef about\n  # \"return\" can be omitted here. It is included for clarity.\n  render \"about.ecr\"\nend\n```\n\nSince this is happening in the \"page\" controller, the view directory for finding the templates will default to `src/views/page/`. We will create the directory and the file \"about.ecr\" in it:\n\n```shell\n$ mkdir -p src/views/page/\n$ vi src/views/page/about.ecr\n\n# Inside the file, we add:\n\nHello, World!\n```\n\nBecause we have called render() without additional arguments, the template will default to being rendered within the default application layout, `views/layouts/application.cr`.\n\nAnd that is it! The request for `/page/about` will reach the router, the router will invoke `PageController.new.about()`, that method will render template `src/views/page/about.ecr` in the context of layout `views/layouts/application.cr`, the result of rendering will be a full page with content `Hello, World!` in the body, and that result will be returned to the client as response body.\n\n# Variables in Views\u003ca name=\"variables_in_views\"\u003e\u003c/a\u003e\n\nAs mentioned, in Amber, templates are compiled and rendered directly in the context of the methods that call `render()`. Those are typically the controller methods themselves, and it means you generally do not need instance variables for passing the information from controllers to views.\n\nAny variable you define in the controller method, instance or local, is directly visible in the template. For example, let's add the date and time and display them on our \"About\" page created in the previous step. The controller method and the corresponding view template would look like this:\n\n```shell\n$ vi src/controllers/page_controller.cr\n\ndef about\n  time = Time.now\n  render \"about.ecr\"\nend\n\n$ vi src/views/page/about.ecr\n\nHello, World! The time is now \u003c%= time %\u003e.\n```\n\nTo further confirm that the templates also implicitly run in the same controller objectthat handled the request, you could place e.g. \"\u003c%= self.class %\u003e in the above example; the response would be \"PageController\". So in addition to seeing the method's local variables, it means that all instance variables and methods existing on the controller object are readily available in the templates as well.\n\n# More on Database Commands\u003ca name=\"more_on_database_commands\"\u003e\u003c/a\u003e\n\n## Micrate\u003ca name=\"micrate\"\u003e\u003c/a\u003e\n\nAmber relies on the shard \"[micrate](https://github.com/amberframework/micrate)\" to perform migrations. The command `amber db` uses \"micrate\" unconditionally. However, some of all the possible database operations are only available through `amber db` and some are only available through invoking `micrate` directly. Therefore, it is best to prepare the application for using both `amber db` and `micrate`.\n\nMicrate is primarily a library so a small piece of custom code is required to provide the minimal `micrate` executable for a project. This is done by placing the following in `src/micrate.cr` (the example is for PostgreSQL but could be trivially adapted to MySQL or SQLite):\n\n```crystal\n#!/usr/bin/env crystal\nrequire \"amber\"\nrequire \"micrate\"\nrequire \"pg\"\n\nMicrate::DB.connection_url = Amber.settings.database_url\nMicrate.logger = Amber.settings.logger.dup\nMicrate.logger.progname = \"Micrate\"\n\nMicrate::Cli.run\n```\n\nAnd by placing the following in `shard.yml` under `targets`:\n\n```\ntargets:\n  micrate:\n    main: src/micrate.cr\n```\n\nFrom there, running `shards build micrate` would build `bin/micrate` which you could use as an executable to access micrate's functionality directly. Please run `bin/micrate -h` to see an overview of its commands.\n\nPlease note that the described procedure sets up `bin/micrate` and `amber db` in a compatible way so these commands can be used cooperatively and interchangeably.\n\nTo have your database migrations run with different credentials than your regular Amber app, simply create new environments in `config/environments/` and prefix your command lines with `AMBER_ENV=...`. For example, you could copy and modify `config/environments/development.yml` into `config/environments/development_admin.yml`, change the credentials as appropriate, and then run migrations as admin using `AMBER_ENV=development_admin ./bin/amber db migrate`.\n\n## Custom Migrations Engine\u003ca name=\"custom_migrations_engine\"\u003e\u003c/a\u003e\n\nWhile `amber db` unconditionally depends on \"micrate\", that's the only place where it makes an assumption about the migrations engine used.\n\nTo use a different migrations engine, such as [migrate.cr](https://github.com/vladfaust/migrate.cr), simply perform all database migration work using the engine's native commands instead of using `amber db`. No other adjustments are necessary, and Amber won't get into your way.\n\n# Internationalization (I18n)\u003ca name=\"internationalization__i18n_\"\u003e\u003c/a\u003e\n\nAmber uses Amber's native shard [citrine-18n](https://github.com/amberframework/citrine-i18n) to provide translation and localization. Even though the shard has been authored by the Amber Framework project, it is Amber-independent and can be used to initialize I18n and determine the visitor's preferred language in any application based on Crystal's HTTP::Server.\n\nThat shard in turn depends on the shard [i18n.cr](https://github.com/TechMagister/i18n.cr) to provide the actual translation and localization functionality.\n\nThe internationalization functionality in Amber is enabled by default. Its setup, initialization, and use basically consist of the following:\n\n1. Initializer file `config/initializers/i18n.cr` where basic I18n settings are defined and `I18n.init` is invoked\n1. Locale files in `src/locales/` and subdirectories where settings for both translation and localization are contained\n1. Pipe named `Citrine::I18n::Handler` which is included in `config/routes.cr` and which detects the preferred language for every request based on the value of the request's HTTP header \"Accept-Language\"\n1. Controller helpers named `t()` and `l()` which provide shorthand access for methods `::I18n.translate` and `::I18n.localize`\n\nOnce the pipe runs on the incoming request, the current request's locale is set in the variable `::I18n.locale`. The value is not stored or copied in any other location and it can be overriden in runtime in any way that the application would require.\n\nFor a locale to be used, it must be requested (or be the default) and exist anywhere under the directory `./src/locales/` with the name `[lang].yml`. If nothing can be found or matched, the locale value will default to \"en\".\n\nFrom there, invoking `t()` and `l()` will perform translation and localization according to the chosen locale. Since these two methods are direct shorthands for methods `::I18n.translate` and `::I18n.localize`, all their usage information and help should be looked up in [i18n.cr's README](https://github.com/TechMagister/i18n.cr).\n\nIn a default Amber application there is a sample localization file `src/locales/en.yml` with one translated string (\"Welcome to Amber Framework!\") which is displayed as the title on the default project homepage.\n\nIn the future, the default/built-in I18n functionality in Amber might be expanded to automatically organize translations and localizations under subdirectories in `src/locales/` when generators are invoked, just like it is already done for e.g. files in `src/views/`. (This functionality already exists in i18n.cr as explained in [i18n.cr's README](https://github.com/TechMagister/i18n.cr), but is not yet used by Amber.)\n\n# Responses\u003ca name=\"responses\"\u003e\u003c/a\u003e\n\n## Responses with Different Content-Type\u003ca name=\"responses_with_different_content_type\"\u003e\u003c/a\u003e\n\nIf you want to provide a different format (or a different response altogether) from the controller methods based on accepted content types, you can use `respond_with` from `Amber::Helpers::Responders`.\n\nOur `about` method from the previous example could be modified in the following way to respond with either HTML or JSON:\n\n```crystal\ndef about\n  respond_with do\n    html render \"about.ecr\"\n    json name: \"John\", surname: \"Doe\"\n  end\nend\n```\n\nSupported format types are `html`, `json`, `xml`, and `text`. For all the available methods and arguments, please see [controller/helpers/responders.cr](https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/responders.cr).\n\n## Error Responses\u003ca name=\"error_responses\"\u003e\u003c/a\u003e\n\n### Manual Error Responses\u003ca name=\"manual_error_responses\"\u003e\u003c/a\u003e\n\nIn any pipe or controller action, you might want to manually return a simple error to the user. This typically means returning an HTTP error code and a short error message, even though you could just as easily print complete pages into the return buffer and return an error code.\n\nTo stop a request during execution and return an error, you would do it this way:\n\n```\nif some_condition_failed\n  Amber.logger.error \"Error! Returning Bad Request\"\n\n  # Status and any headers should be set before writing response body\n  context.response.status_code = 400\n\n  # Finally, write response body\n  context.response.puts \"Bad Request\"\n\n\n  # Another way to do the same and respond with a text/plain error\n  # is to use Crystal's respond_with_error():\n  context.response.respond_with_error(\"Bad Request\", 400)\n\n  return\nend\n```\n\nPlease note that you must use `context.response.puts` or `context.response\u003c\u003c` to print to the output buffer in case of errors. You cannot set the error code and then call `return \"Bad Request\"` because the return value will not be added to response body if HTTP code is not 2xx.\n\n### Error Responses via Error Pipe\u003ca name=\"error_responses_via_error_pipe\"\u003e\u003c/a\u003e\n\nThe above example for manually returning error responses does not involve raising any Exceptions \u0026mdash; it simply consists of setting the status and response body and returning them to the client.\n\nAnother approach to returning errors consists in using Amber's error subsystem. It automatically provides you with a convenient way to raise Exceptions and return them to the client properly wrapped in application templates etc.\n\nThis method relies on the fact that pipes call next pipes in a row explicitly, and so the method call chain is properly established. In turn, this means that e.g. raising an exception in your controller can be rescued by an earlier pipe in a row that wrapped the call to `call_next(context)` inside a `begin...rescue` block.\n\nAmber contains a generic pipe named \"Errors\" for handling errors. It is activated by using the line `plug Amber::Pipe::Error.new` in your `config/routes.cr`.\n\nTo be able to extend the list of errors or modify error templates yourself, you should first run `amber g error` to copy the relevant files to your application. In principle, running this command will get you the files `src/pipes/error.cr`, `src/controllers/error_controller.cr`, and `src/views/error/`, all of which can be modified to suit your needs.\n\nTo see the error subsystem at work, you could now do something as simple as:\n\n```crystal\nclass HomeController \u003c ApplicationController\n  def index\n    raise Exception.new \"No pass!\"\n    render(\"index.slang\")\n  end\nend\n```\n\nAnd then visit [http://localhost:3000/](http://localhost:3000/). You would see a HTTP 500 (Internal Server Error) containing the specified error message, but wrapped in an application template rather than printed plainly like the most basic HTTP errors.\n\n# Assets Pipeline\u003ca name=\"assets_pipeline\"\u003e\u003c/a\u003e\n\nIn an Amber project, raw assets are in `src/assets/`:\n\n```shell\napp/src/assets/\napp/src/assets/fonts\napp/src/assets/images\napp/src/assets/images/logo.svg\napp/src/assets/javascripts\napp/src/assets/javascripts/main.js\napp/src/assets/stylesheets\napp/src/assets/stylesheets/main.scss\n\n```\n\nAt build time, all these are processed and placed under `public/dist/`.\nThe JS resources are bundled to `main.bundle.js` and CSS resources are bundled to `main.bundle.css`.\n\n[Webpack](https://webpack.js.org/) is used for asset management.\n\nTo include additional .js or .css/.scss files you would generally add `import \"../../file/path\";` statements to `src/assets/javascripts/main.js`. You add both JS and CSS includes into `main.js` because webpack only processes import statements in .js files. So you must add the CSS import lines to a .js file, and as a result, this will produce a JS bundle that contains both JS and CSS data in it. Then, webpack's plugin named ExtractTextPlugin (part of default configuration) is used to extract CSS parts into their own bundle.\n\nThe base/common configuration for all this is in `config/webpack/common.js`.\n\n## Adding jQuery and jQuery UI\u003ca name=\"adding_jquery_and_jquery_ui\"\u003e\u003c/a\u003e\n\nAs an example, we can add the jQuery and jQuery UI libraries to an Amber project.\n\nPlease note that we are going to unpack the jQuery UI zip file directly into `src/assets/javascripts/` even though it contains some CSS and images. This is done because splitting the different asset types out to individual directories would be harder to do and maintain over time (e.g. paths in jQuery UI CSS files pointing to \"images/\" would no longer work, and updating the version later would be more complex).\n\nThe whole procedure would be as follows:\n\n```bash\ncd src/assets/javascripts\n\n# Download jQuery\nwget https://code.jquery.com/jquery-3.3.1.js\n\n# Then download jQuery UI from http://jqueryui.com/download/ to the same/current directory\n# and unpack it:\nunzip jquery-ui-1.12.1.custom.zip\n\n# Then edit main.js and add the import lines:\nimport './jquery-3.3.1.min.js'\nimport './jquery-ui-1.12.1.custom/jquery-ui.css'\nimport './jquery-ui-1.12.1.custom/jquery-ui.js'\nimport './jquery-ui-1.12.1.custom/jquery-ui.structure.css'\nimport './jquery-ui-1.12.1.custom/jquery-ui.theme.css'\n\n# And finally, edit ../../../config/webpack/common.js to add jquery resource alias:\n  resolve: {\n    alias: {\n      amber: path.resolve(__dirname, '../../lib/amber/assets/js/amber.js'),\n      jquery: path.resolve(__dirname, '../../src/assets/javascripts/jquery-3.3.1.min.js')\n    }\n```\n\nAnd that's it. At the next application build (e.g. with `amber watch`) all the mentioned resources and images will be compiled, placed to `public/dist/`, and included in the CSS/JS files.\n\n## Resource Aliases\u003ca name=\"resource_aliases\"\u003e\u003c/a\u003e\n\nSometimes, the code or libraries you include will in turn require other libraries by their generic name, e.g. \"jquery\". Since a file named \"jquery\" does not actually exist on disk (or at least not in the location that is searched), this could result in an error such as:\n\n```\nERROR in ./src/assets/javascripts/jquery-ui-1.12.1.custom/jquery-ui.js\nModule not found: Error: Can't resolve 'jquery' in '.../src/assets/javascripts/jquery-ui-1.12.1.custom'\n @ ./src/assets/javascripts/jquery-ui-1.12.1.custom/jquery-ui.js 5:0-26\n  @ ./src/assets/javascripts/main.js\n```\n\nThe solution is to add resource aliases to webpack's configuration which will instruct it where to find the real files if/when they are referenced by their alias.\n\nFor example, to resolve \"jquery\", you would add the following to the \"resolve\" section in `config/webpack/common.js`:\n\n```\n...\n  resolve: {\n    alias: {\n      jquery: path.resolve(__dirname, '../../src/assets/javascripts/jquery-3.3.1.min.js')\n    }\n  }\n...\n```\n\n## CSS Optimization / Minification\u003ca name=\"css_optimization___minification\"\u003e\u003c/a\u003e\n\nYou might want to minimize the CSS that is output to the final CSS bundle.\n\nTo do so you need an entry under \"devDependencies\" in the project's file `package.json`:\n\n```\n    \"optimize-css-assets-webpack-plugin\": \"^1.3.0\",\n```\n\nAnd an entry at the top of `config/webpack/common.js`:\n\n```\nconst OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin');\n```\n\nAnd you need to run `npm install` for the plugin to be installed (saved to \"node_modules/\" subdirectory).\n\n## File Copying\u003ca name=\"file_copying\"\u003e\u003c/a\u003e\n\nYou might also want to copy some of the files from their original location to `public/dist/` without doing any modifications in the process. This is done by adding the following under \"devDependencies\" in `package.json`:\n\n```\n    \"copy-webpack-plugin\": \"^4.1.1\",\n```\n\nTo do so you need following at the top of `config/webpack/common.js`:\n\n```\nconst CopyWebpackPlugin = require('copy-webpack-plugin');\n```\n\nAnd the following under \"plugins\" section down below in the file:\n\n```\n  new CopyWebPackPlugin([\n    {\n      from: path.resolve(__dirname, '../../vendor/images/'),\n      to: path.resolve(__dirname, '../../public/dist/images/'),\n      ignore: ['.*'],\n    }\n  ]),\n```\n\nAnd as usual, you need to run `npm install` for the plugin to be installed (saved to \"node_modules/\" subdirectory).\n\n## Asset Management Alternatives\u003ca name=\"asset_management_alternatives\"\u003e\u003c/a\u003e\n\nMaybe it would be useful to replace Webpack with e.g. [Parcel](https://parceljs.org/). (Finding a non-js/non-node/non-npm application for this purpose would be even better; please let me know if you know one.)\n\nIn general it seems it shouldn't be much more complex than replacing the command to run and development dependencies in project's `package.json` file.\n\n# Advanced Topics\u003ca name=\"advanced_topics\"\u003e\u003c/a\u003e\n\nWhat follows is a collection of advanced topics which can be read or skipped on an individual basis.\n\n## Amber::Controller::Base\u003ca name=\"amber__controller__base\"\u003e\u003c/a\u003e\n\nThis is the base controller from which all other controllers inherit. Source file is in [controller/base.cr](https://github.com/amberframework/amber/blob/master/src/amber/controller/base.cr).\n\nOn 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.\n\nThe content of this controller and the methods it gets from including other modules are intuitive enough to be copied here and commented where necessary:\n\n```crystal\nrequire \"http\"\n\nrequire \"./filters\"\nrequire \"./helpers/*\"\n\nmodule Amber::Controller\n  class Base\n    include Helpers::CSRF\n    include Helpers::Redirect\n    include Helpers::Render\n    include Helpers::Responders\n    include Helpers::Route\n    include Helpers::I18n\n    include Callbacks\n\n    protected getter context : HTTP::Server::Context\n    protected getter params : Amber::Validators::Params\n\n    delegate :logger, to: Amber.settings\n\n    delegate :client_ip,\n      :cookies,\n      :delete?,\n      :flash,\n      :format,\n      :get?,\n      :halt!,\n      :head?,\n      :patch?,\n      :port,\n      :post?,\n      :put?,\n      :request,\n      :requested_url,\n      :response,\n      :route,\n      :session,\n      :valve,\n      :websocket?,\n      to: context\n\n    def initialize(@context : HTTP::Server::Context)\n      @params = Amber::Validators::Params.new(context.params)\n    end\n  end\nend\n\n```\n\n[Helpers::CSRF](https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/csrf.cr) module provides:\n\n```crystal\n    def csrf_token\n    def csrf_tag\n    def csrf_metatag\n```\n\n[Helpers::Redirect](https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/redirect.cr) module provides:\n\n```crystal\n    def redirect_to(location : String, **args)\n    def redirect_to(action : Symbol, **args)\n    def redirect_to(controller : Symbol | Class, action : Symbol, **args)\n    def redirect_back(**args)\n```\n\n[Helpers::Render](https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/render.cr) module provides:\n\n```crystal\n    LAYOUT = \"application.slang\"\n    macro render(template = nil, layout = true, partial = nil, path = \"src/views\", folder = __FILE__)\n```\n\n[Helpers::Responders](https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/responders.cr) helps control what final status code, body, and content-type will be returned to the client.\n\n[Helpers::Route](https://github.com/amberframework/amber/blob/master/src/amber/controller/helpers/route.cr) module provides:\n\n```crystal\n    def action_name\n    def route_resource\n    def route_scope\n    def controller_name\n```\n\n[Callbacks](https://github.com/amberframework/amber/blob/master/src/amber/dsl/callbacks.cr) module provides:\n\n```crystal\n    macro before_action\n    macro after_action\n```\n\n## Extensions\u003ca name=\"extensions\"\u003e\u003c/a\u003e\n\nAmber adds some very convenient extensions to the existing String and Number classes. The extensions are in the [extensions/](https://github.com/amberframework/amber/tree/master/src/amber/extensions) directory. They are useful in general, but particularly so when writing param validation rules. Here's the listing of currently available extensions:\n\nFor String:\n\n```crystal\n      def str?\n      def email?\n      def domain?\n      def url?\n      def ipv4?\n      def ipv6?\n      def mac_address?\n      def hex_color?\n      def hex?\n      def alpha?(locale = \"en-US\")\n      def numeric?\n      def alphanum?(locale = \"en-US\")\n      def md5?\n      def base64?\n      def slug?\n      def lower?\n      def upper?\n      def credit_card?\n      def phone?(locale = \"en-US\")\n      def excludes?(value)\n      def time_string?\n\n```\n\nFor Number:\n\n```crystal\n      def positive?\n      def negative?\n      def zero?\n      def div?(n)\n      def above?(n)\n      def below?(n)\n      def lt?(num)\n      def self?(num)\n      def lteq?(num)\n      def between?(range)\n      def gteq?(num)\n\n```\n\n## Shards\u003ca name=\"shards\"\u003e\u003c/a\u003e\n\nAmber and all of its components depend on the following shards:\n\n```\n--------SHARD--------------------SOURCE---DESCRIPTION------------------------------------------------------\n------- Web, Routing, Templates, Mailers, Plugins ---------------------------------------------------------\nrequire \"amber\"                  AMBER    Amber itself\nrequire \"amber_router\"           AMBER    Request router implementation\nrequire \"citrine-18n\"            AMBER    Translation and localization\nrequire \"http\"                   CRYSTAL  Lower-level supporting HTTP functionality\nrequire \"http/client\"            CRYSTAL  HTTP Client\nrequire \"http/headers\"           CRYSTAL  HTTP Headers\nrequire \"http/params\"            CRYSTAL  Collection of HTTP parameters and their values\nrequire \"http/server\"            CRYSTAL  HTTP Server\nrequire \"http/server/handler\"    CRYSTAL  HTTP Server's support for \"handlers\" (middleware)\nrequire \"quartz_mailer\"          AMBER    Sending and receiving emails\nrequire \"email\"                  EXTERNAL Simple email sending library\nrequire \"teeplate\"               AMBER    Rendering multiple template files\n\n------- Databases and ORM Models --------------------------------------------------------------------------\nrequire \"big\"                    EXTERNAL BigRational for numeric. Retains precision, requires LibGMP\nrequire \"db\"                     CRYSTAL  Common DB API\nrequire \"pool/connection\"        CRYSTAL  Part of Crystal's common DB API\nrequire \"granite_orm/adapter/\u003c%- @database %\u003e\" AMBER Granite's DB-specific adapter\nrequire \"micrate\"                EXTERNAL Database migration tool\nrequire \"mysql\"                  CRYSTAL  MySQL connector\nrequire \"pg\"                     EXTERNAL PostgreSQL connector\nrequire \"redis\"                  EXTERNAL Redis client\nrequire \"sqlite3\"                EXTERNAL SQLite3 bindings\n\n------- Template Rendering --------------------------------------------------------------------------------\nrequire \"crikey\"                 EXTERNAL Template language, Data structure view, inspired by Hiccup\nrequire \"crustache\"              EXTERNAL Template language, {{Mustache}} for Crystal\nrequire \"ecr\"                    CRYSTAL  Template language, Embedded Crystal (ECR)\nrequire \"kilt\"                   EXTERNAL Generic template interface\nrequire \"kilt/slang\"             EXTERNAL Kilt support for Slang template language\nrequire \"liquid\"                 EXTERNAL Template language, used by Amber for recipe templates\nrequire \"slang\"                  EXTERNAL Template language, inspired by Slim\nrequire \"temel\"                  EXTERNAL Template language, extensible Markup DSL\n\n------- Command Line, Logs, and Output --------------------------------------------------------------------\nrequire \"cli\"                    EXTERNAL Support for building command-line interface applications\nrequire \"colorize\"               CRYSTAL  Changing colors and text decorations\nrequire \"logger\"                 CRYSTAL  Simple but sophisticated logging utility\nrequire \"optarg\"                 EXTERNAL Parsing command-line options and arguments\nrequire \"option_parser\"          CRYSTAL  Command line options processing\nrequire \"shell-table\"            EXTERNAL Creating text tables in command line terminal\n\n------- Formats, Protocols, Digests, and Compression ------------------------------------------------------\nrequire \"digest/md5\"             CRYSTAL  MD5 digest algorithm\nrequire \"html\"                   CRYSTAL  HTML escaping and unescaping methods\nrequire \"jasper_helpers\"         AMBER    Helper functions for working with HTML\nrequire \"json\"                   CRYSTAL  Parsing and generating JSON documents\nrequire \"openssl\"                CRYSTAL  OpenSSL integration\nrequire \"openssl/hmac\"           CRYSTAL  Computing Hash-based Message Authentication Code (HMAC)\nrequire \"openssl/sha1\"           CRYSTAL  OpenSSL SHA1 hash functions\nrequire \"yaml\"                   CRYSTAL  Serialization and deserialization of YAML 1.1\nrequire \"zlib\"                   CRYSTAL  Reading/writing Zlib compressed data as specified in RFC 1950\n\n------- Supporting Functionality --------------------------------------------------------------------------\nrequire \"base64\"                 CRYSTAL  Encoding and decoding of binary data using base64 representation\nrequire \"bit_array\"              CRYSTAL  Array data structure that compactly stores bits\nrequire \"callback\"               EXTERNAL Defining and invoking callbacks\nrequire \"compiled_license\"       EXTERNAL Compile in LICENSE files from project and dependencies\nrequire \"compiler/crystal/syntax/*\" CRYSTAL Crystal syntax parser\nrequire \"crypto/bcrypt/password\" CRYSTAL  Generating, reading, and verifying Crypto::Bcrypt hashes\nrequire \"crypto/subtle\"          CRYSTAL\nrequire \"file_utils\"             CRYSTAL  Supporting functions for files and directories\nrequire \"i18n\"                   EXTERNAL Underlying I18N shard for Crystal\nrequire \"inflector\"              EXTERNAL Inflector for Crystal (a port of Ruby's ActiveSupport::Inflector)\nrequire \"process\"                CRYSTAL  Supporting functions for working with system processes\nrequire \"random/secure\"          CRYSTAL  Generating random numbers from a secure source provided by system\nrequire \"selenium\"               EXTERNAL Selenium Webdriver client\nrequire \"socket\"                 CRYSTAL  Supporting functions for working with sockets\nrequire \"socket/tcp_socket\"      CRYSTAL  Supporting functions for TCP sockets\nrequire \"socket/unix_socket\"     CRYSTAL  Supporting functions for UNIX sockets\nrequire \"string_inflection/kebab\"EXTERNAL Singular/plurals words in \"kebab\" style (\"foo-bar\")\nrequire \"string_inflection/snake\"EXTERNAL Singular/plurals words in \"snake\" style (\"foo_bar\")\nrequire \"uri\"                    CRYSTAL  Creating and parsing URI references as defined by RFC 3986\nrequire \"uuid\"                   CRYSTAL  Functions related to Universally unique identifiers (UUIDs)\nrequire \"weak_ref\"               CRYSTAL  Weak Reference class allowing referenced objects to be GC-ed\nrequire \"zip\"                    EXTERNAL Zip compression library, used for fetching zipped recipes\n\nrequire \"ecr\"\nrequire \"markd\"\nrequire \"exception_page\"\n```\n\n\nOnly the parts that are used end up in the compiled project.\n\n## Environments\u003ca name=\"environments\"\u003e\u003c/a\u003e\n\nAfter \"[amber](https://github.com/amberframework/amber/blob/master/src/amber.cr)\" shard is loaded, `Amber` module automatically includes [Amber::Environment](https://github.com/amberframework/amber/blob/master/src/amber/environment.cr) which adds the following methods:\n\n```\nAmber.settings         # Singleton object, contains current settings\nAmber.logger           # Alias for Amber.settings.logger\nAmber.env, Amber.env=  # Env (environment) object (development, production, test)\n```\n\nThe list of all available application settings is in [Amber::Environment::Settings](https://github.com/amberframework/amber/blob/master/src/amber/environment/settings.cr). These settings are loaded from the application's `config/environment/\u003cname\u003e.yml` file and are then overriden by any settings in `config/application.cr`'s `Amber::Server.configure` block.\n\n[Env](https://github.com/amberframework/amber/blob/master/src/amber/environment/env.cr) (`amber.env`) also provides basic methods for querying the current environment:\n```crystal\n    def initialize(@env : String = ENV[AMBER_ENV]? || \"development\")\n    def in?(env_list : Array(EnvType))\n    def in?(*env_list : Object)\n    def to_s(io)\n    def ==(env2 : EnvType)\n\n```\n\n## Starting the Server\u003ca name=\"starting_the_server\"\u003e\u003c/a\u003e\n\nIt is important to explain exactly what happens from the time you run the application til Amber starts serving user requests:\n\n1. `crystal src/\u003capp_name\u003e.cr` - you or a script starts Amber\n\t1. `require \"../config/*\"` - as the first thing, `config/*` is required. Inclusion is in alphabetical order. Crystal only looks for *.cr files and only files in config/ are loaded (no subdirectories)\n\t\t1. `require \"../config/application.cr\"` - this is usually the first file in `config/`\n\t\t\t1. `require \"./initializers/**\"` - loads all initializers. There is only one initializer file by default, named `initializer/database.cr`. Here we have a double star (\"**\") meaning inclusion of all files including in subdirectories. Inclusion order is always \"current directory first, then subdirectories\"\n\t\t\t1. `require \"amber\"` - Amber itself is loaded\n\t\t\t\t1. Loading Amber makes `Amber::Server` class available\n\t\t\t\t1. `include Amber::Environment` - already in this stage, environment is determined and settings are loaded from yml file (e.g. from `config/environments/development.yml`. Settings are later available as `settings`\n\t\t\t1. `require \"../src/controllers/application_controller\"` - main controller is required. This is the base class for all other controllers\n\t\t\t\t1. It defines `ApplicationController`, includes JasperHelpers in it, and sets default layout (\"application.slang\").\n\t\t\t1. `require \"../src/controllers/**\"` - all other controllers are loaded\n\t\t\t1. `Amber::Server.configure` block is invoked to override any config settings\n\t\t1. `require \"config/routes.cr\"` - this again invokes `Amber::Server.configure` block, but concerns itself with routes and feeds all the routes in\n\t1. `Amber::Server.start` is invoked\n\t\t1. `instance.run` - implicitly creates a singleton instance of server, saves it to `@@instance`, and calls `run` on it\n\t\t1. Consults variable `settings.process_count`\n\t\t1. If process count is 1, `instance.start` is called\n\t\t1. If process count is \u003e 1, the desired number of processes is forked, while main process enters sleep\n\t\t\t1. 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).\n\t\t1. `instance.start` is called for every process\n\t\t\t1. It saves current time and prints startup info\n\t\t\t1. `@handler.prepare_pipelines` is called. @handler is Amber::Pipe::Pipeline, a subclass of Crystal's [HTTP::Handler](https://crystal-lang.org/api/0.24.1/HTTP/Handler.html). `prepare_pipelines` 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.\n\t\t\t1. `server = HTTP::Server.new(host, port, @handler)`- Crystal's HTTP server is created\n\t\t\t1. `server.tls = Amber::SSL.new(...).generate_tls if ssl_enabled?`\n\t\t\t1. Signal::INT is trapped (calls `server.close` when received)\n\t\t\t1. `loop do server.listen(settings.port_reuse) end` - server enters main loop\n\n## Serving Requests\u003ca name=\"serving_requests\"\u003e\u003c/a\u003e\n\nSimilarly as with starting the server, is important to explain exactly what is happening when Amber is serving requests:\n\nAmber's app serving model is based on Crystal's built-in, underlying functionality:\n\n1. The server that is running is an instance of Crystal's\n\t [HTTP::Server](https://crystal-lang.org/api/0.24.1/HTTP/Server.html)\n2. On every incoming request, a \"handler\" is invoked. As supported by Crystal, handler can be a simple Proc or an instance of [HTTP::Handler](https://crystal-lang.org/api/0.24.1/HTTP/Handler.html). 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 currently at least two of them are always pre-defined \u0026mdash; pipes named \"Pipeline\" and \"Controller\". The pipe \"Pipeline\" always executes first; it determines which pipeline the request is meant for and runs the first pipe in that pipeline. The pipe \"Controller\" always executes last; it consults the routing table, instantiates the appropriate controller, and invokes the appropriate method on it\n3. In the pipeline, every Pipe (Amber::Pipe::*, ultimately subclass of Crystal's [HTTP::Handler](https://crystal-lang.org/api/0.24.2/HTTP/Handler.html)) is invoked with one argument. That argument is\n\t by convention called \"context\" and it is an instance of `HTTP::Server::Context`. By default it has two built-in methods \u0026mdash; `request` and `response`, containing the request and response parts respectively. On top of that, Amber adds various other methods and variables, such as `router`, `flash`, `cookies`, `session`, `content`, `route`, `client_ip`, and others as seen in [router/context.cr](https://github.com/amberframework/amber/blob/master/src/amber/router/context.cr) and [extensions/http.cr](https://github.com/amberframework/amber/blob/master/src/amber/extensions/http.cr)\n4. Please note that calling the chain of pipes is not automatic; every pipe needs to call `call_next(context)` 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 `Amber::Pipe::Controller` is always implicitly added as the last pipe, so in the context of your pipes the next one always exists. State between pipes is not passed via separate variables but via modifying `context` and the data contained in it. Context persists for the duration of the request. Context persists for the duration of the request\n\nAfter that, pipelines, pipes, routes, and other Amber-specific parts come into play.\n\nSo, in detail, from the beginning:\n\n1. `loop do server.listen(settings.port_reuse) end` - main loop is running\n\t1. `spawn handle_client(server.accept?)` - handle_client() is called in a new fiber after connection is accepted\n\t\t1. `io = OpenSSL::SSL::Socket::Server.new(io, tls, sync_close: true) if @tls`\n\t\t1. `@processor.process(io, io)`\n\t\t\t1. `if request.is_a?(HTTP::Request::BadRequest); response.respond_with_error(\"Bad Request\", 400)`\n\t\t\t1. `response.version = request.version`\n\t\t\t1. `response.headers[\"Connection\"] = \"keep-alive\" if request.keep_alive?`\n\t\t\t1. `context = Context.new(request, response)` - this context is already extended by Amber with additional properties and methods\n\t\t\t1. `@handler.call(context)` - `Amber::Pipe::Pipeline.call()` is called\n\t\t\t\t1. `raise ...error... if context.invalid_route?` - route validity is checked early\n\t\t\t\t1. `if context.websocket?; context.process_websocket_request` - if websocket, parse as such\n\t\t\t\t1. `elsif ...; ...pipeline.first...call(context)` - if regular HTTP request, call the first handler in the appropriate pipeline\n\t\t\t\t\t1. `call_next(context)` - each pipe calls call_next(context) somewhere during its execution, and all pipes are executed\n\t\t\t\t\t\t1. `context.process_request` - the always-last pipe (Amber::Pipe::Controller) calls `process_request` to dispatch the action to controller. After that last pipe, the stack of call_next()s is \"unwound\" back to the starting position\n\t\t\t\t\t1. `context.finalize_response` - minor final adjustments to response are made (headers are added, and response body is printed unless action was HEAD)\n\n## Support Routines\u003ca name=\"support_routines\"\u003e\u003c/a\u003e\n\nIn [support/](https://github.com/amberframework/amber/tree/master/src/amber/support) directory there is a number of various support files that provide additional, ready-made routines.\n\nCurrently, the following can be found there:\n\n```\nclient_reload.cr      - Support for reloading developer's browser\n\nfile_encryptor.cr     - Support for storing/reading encrypted versions of files\nmessage_encryptor.cr\nmessage_verifier.cr\n\nlocale_formats.cr     - Very basic locate data for various manually-added locales\n\nmime_types.cr         - List of MIME types and helper methods for working with them:\n\n                        def self.mime_type(format, fallback = DEFAULT_MIME_TYPE)\n                        def self.zip_types(path)\n                        def self.format(accepts)\n                        def self.default\n                        def self.get_request_format(request)\n```\n\n## Amber behind a Load Balancer | Reverse Proxy | ADC\u003ca name=\"amber_behind_a_load_balancer___reverse_proxy___adc\"\u003e\u003c/a\u003e\n\n(In this section, the terms \"Load Balancer\", \"Reverse Proxy\", \"Proxy\", and \"Application Delivery Controller\" (ADC) are used interchangeably.)\n\nBy default, in development environment Amber listens on port 3000, and in production environment it listens on port 8080. This makes it very easy to run a load balancer on ports 80 (HTTP) and 443 (HTTPS) and proxy user requests to Amber.\n\nThere are three groups of benefits of running Amber behind a proxy:\n\nOn a basic level, a proxy will perform TCP and HTTP normalization \u0026mdash; it will filter out invalid TCP packets, flags, window sizes, sequence numbers, and SYN floods. It will only pass valid HTTP requests through (protecting the application from protocol-based attacks) and smoothen out deviations which are tolerated by HTTP specification (such as multi-line HTTP headers). Finally, it will provide HTTP/2 support for your application and perform SSL and compression offloading so that these functions are done on the load balancers rather than on the application servers.\n\nAlso, as an important implementation-specific detail, Crystal currently does not provide applications with the information on the client IPs that are making HTTP requests. Therefore, Amber is by default unaware of them. With a proxy in front of Amber and using Amber's pipe `ClientIp`, the client IP information will be passed from the proxy to Amber and be available as `context.client_ip.address`.\n\nOn an intermediate level, a proxy will provide you with caching and scaling and serve as a versatile TCP and HTTP load balancer. It will cache static files, route your application and database traffic to multiple backend servers, balance multiple protocols based on any criteria, fix and rewrite HTTP traffic, and so on. The benefits of starting application development with acceleration and scaling in mind from the get-go are numerous.\n\nOn an advanced level, a proxy will allow you to keep track of arbitrary statistics and counters, perform GeoIP offloading and rate limiting, filter out bots and suspicious web clients, implement DDoS protection and web application firewall, troubleshoot network conditions, and so on.\n\n[HAProxy](www.haproxy.org) is an excellent proxy to use and to run it you will only need the `haproxy` binary, two command line options, and a config file. A simple HAProxy config file that can be used out of the box is available in [support/haproxy.conf](https://github.com/docelic/amber-introduction/blob/master/support/haproxy.conf). This config file will be expanded over time into a full-featured configuration to demonstrate all of the above-mentioned points, but even by default the configuration should be good enough to get you started with practical results.\n\nHAProxy comes pre-packaged for most GNU/Linux distributions and MacOS, but if you do not see version 1.8.x available, it is recommended to manually install the latest stable version.\n\n\u003ca name=\"install-haproxy\"\u003e\u003c/a\u003eTo compile the latest stable HAProxy from source, you could use the following procedure:\n\n```\ngit clone http://git.haproxy.org/git/haproxy-1.8.git/\ncd haproxy-1.8\nmake -j4 TARGET=linux2628 USE_OPENSSL=1\n```\n\nThe compilation will go trouble-free and you will end up with the binary named `haproxy` in the current directory.\n\nTo obtain the config file and set up the basic directory structure, please run the following in your Amber app directory:\n\n```sh\ncd config\nwget https://raw.githubusercontent.com/docelic/amber-introduction/master/support/haproxy.conf\ncd ..\nmkdir -p var/{run,empty}\n```\n\nAnd finally, to start HAProxy in development/foreground mode, please run:\n\n```sh\nsudo ../haproxy-1.8/haproxy -f config/haproxy.conf -d\n```\n\nAnd then start `amber watch` and point your browser to [http://localhost/](http://localhost/) instead of [http://localhost:3000/](http://localhost:3000/)!\n\nPlease also note that this HAProxy configuration enables the built-in HAProxy status page at [http://localhost/server-status](http://localhost/server-status) and restricts access to it to localhost.\n\nWhen you confirm everything is working, you can omit the `-d` flag and it will start HAProxy in background, returning the shell back to you. You can then forget about HAProxy until you modify its configuration and want to reload it. Then simply call `kill -USR2 var/run/haproxy.pid`.\n\nFinally, now that we are behind a proxy, to get access to client IPs we can enable the following line in `config/routes.cr`:\n\n```\n    plug Amber::Pipe::ClientIp.new([\"X-Forwarded-For\"])\n```\n\nAnd we can modify one of the views to display the user IP address. Assuming you are using slang, you could edit the default view file `src/views/home/index.slang` and add the following to the bottom to confirm the new behavior:\n\n```\n    a.list-group-item.list-group-item-action href=\"#\" = \"IP Address: \" + ((ip = context.client_ip) ? ip.address : \"Unknown\")\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdocelic%2Famber-introduction","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdocelic%2Famber-introduction","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdocelic%2Famber-introduction/lists"}