Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Learn how to get started with Azu and build applications
Let's get a Azu application up and running as quickly as possible.
Before we begin, please take a minute to read the Installation Guide. By installing any necessary dependencies beforehand, we'll be able to get our application up and running smoothly.
We can run crystal init app my_app
from any directory in order to bootstrap our Azu application. Assuming that the name of our application is my_app
, let's run the following command:
shard.yml
Open your /my_app/shard.yml in your favorite editor and add the azu dependency
Azu is very light, flexible and module, for this reason it does not add front-end dependencies nor database dependencies. We will teach you how you can integrate those later in the guides
Now install Azu by running shards install
from the terminal
Now that you have install Azu, lets enable it in your project. Open /my_app/src/my_app.cr
With the simple include azu
you have unlocked the benefits of Azu for your project.
Name
MyApp.configure
MyApp.log
This is the application logger and uses Crystal built in logger by default
MyApp.env
Allows you to work with the application current environment Production, Development, Staging, etc.
MyApp.config
Gets your application configuration for easy access from other parts of the code base
MyApp.start
Starts the HTTP server
Accepts a block and allows you to define Azu
The only requirement to use Azu is the Crystal Language itself
In order to build a full-featured Azu application, you will need a few dependencies installed.
the Crystal Programming Language - installation instruction can be found here
Shards command line tool for package management (Installed by default with Crystal installation)
a database - Azu recommends PostgreSQL but you can pick others or not use a database at all
Node.js for assets - which can be opt-out, especially if you are building APIs
Add the dependency to your shard.yml
:
Run shards install
Summary
At the end of this section, you must have installed Crystal, Shards, PostgreSQL and Node.js. Now that we have everything installed, let's define our first Azu application.
Access request parameters from the request body, query string, path
Params, short for parameters, allow you to access data sent by the client with the request. Requests can have parameters in different places, intuitively the params can be accessed by the attribute context location:
Params can come from:
Request path (eg. /books/:id
)
Query string (eg. /books?title=Hanami
)
Request body (eg. a POST
request to /books
)
Other than Path and Query parameters, all other parameters are parsed depending on the "Content-Type" header
Params provide hash-like access to the request attributes. Since attributes can be expected in different contexts of the request such as path, body, and query, the lookup happens in the following order of precedence.
Form
Path
Query
To access the value of a param, we can use the subscriber operator #[]
.
Given an Endpoint with a path of "/users/:id"
If we visit /users/john
, we should see path string:
john
.
If you're writing a web service application, you might find yourself more comfortable accepting parameters in JSON format. If the "Content-Type" header of your request is set to "application/json", will automatically be loaded into the params.json
object.
So for example, if you are sending this JSON content:
The params object will contain a string with the content of the JSON.
Note: Azu does not convert the params.json string into a JSON ANY object, and instead the developer can decide how to best parse.
When we use crystal init app my_app
to generates a new Azu application, it generated an empty Crystal App directory structure.
Many frameworks build a default directory structure to start with. Azu does not implement a default set of conventions, and instead provides you with patterns that fits best your needs.
AZU is a set of tools that offers building blocks to create Crystal Applications requiring little to no boilerplate code making you more efficient and productive.
Azu was created by harvesting, it started by not trying to build a framework, but by building an application. While you build the application you don't try to develop generic code, but you do work hard to build a well-factored and well-designed application.
Plain Crystal, little to no DSL
Small DSL, Plain Old Crystal
No magic, no surprises
Type-safe definitions
Adopts to your architectural pattern
Model, View, Controller
Modular
Pipes and Filters
Event-Driven
Layered
With one application built you then build another application that has at least some similar needs to the first one. While doing we pay attention to any duplication between the second and first application. As you find duplication you factor out into a common area, this common area is Azu Toolkit
Azu helps you to have the clarity to separate and represent the Input and Output at every step of the design lifecycle. Azu does this by mapping objects' names to the actual technical names so you start to build a mental model on how the toolkit enables you, at the same time understand the design process.
All applications will have business-specific use cases for which the application has been built, a pattern that we recommend to follow for building any application.
Plain Old Crystal Objects (POCO) - Also known as Value Objects are used to pass immutable data around your domain.
Use Cases - expect simple request data structures, for its inputs and produces simple response data structures for its output.
Endpoints is a simple module that defines the resource, and allows you to access the Request object and build the Response.
Endpoints is a simple module that defines the resource, and allows you to access the Request object and build the Response.
The endpoint is the final stage of the request process Each endpoint is the location from which APIs can access the resources of your application to carry out their function.
To ensure correctness Endpoints are designed with the Request and Response pattern in mind you can think of it as input and output to a function, where the request is the input and the response is the output.
Request and Response objects are type-safe objects that can be designed by contract.
An Endpoint requires at a minimum a Request and Response Object
The endpoint gives you access to a wide rage of functions to allow you inspect the request and modify the response.
Method
Description
params
context
method
HTTP Request Method
header
Gets all HTTP Headers
json
Gets request body as string
cookies
Gets request cookies
content_type(type : String)
Sets the content type of the response
header(key : String, value : String)
Sets response header
redirect(to location : String, status : Int32 = 301)
Redirect requests to a new resource
cookie(cookie : HTTP::Cookie)
Sets response cookie
status(status : Int32)
Sets response status
error(message : String, status : Int32 = 400, errors = [] of String)
Renders a response error
Every endpoint must define a call method that returns a Response object.
A request object encapsulates the structure of the incoming HTTP request as needed per your application, allowing you to inspect, validate and ensuring correctness. Read more about Requests
The endpoint will contain a user_request method that returns an instance of the UserRequest object.
Read more about Requests
The most basic Azu routes accept a URI and a HTTP::Handler.
Routes can be defined withing Endpoints. Having routes defined close to the class that uses it allows for code cohesiveness and readability
All HTTP verbs are available as macro methods within Endpoints.
For flexibility Azu allows you to define routes outside of Endpoints.
Azu configuration properties are minimal this is to help you keep everything in a mental model without overloading your brain.
Azu configuration properties are minimal this is to help you keep everything in a mental model without overloading your brain.
Configuration properties lives within your application's main file.
Apps sometimes store config as constants in the code. This is a violation of the twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not.
We recommend for development use .env files.
.env
file
.env.local
file
.env.#{CRYSTAL_ENV}
file
.env.#{CRYSTAL_ENV}.local
file
Important: Azu does NOT load the .env
files automatically. It is up to you the developer to use your preferred method to load environment variables.
By default Azu ships with the following environments:
Build
Development
Test
Integration
Acceptance
Pipeline
Staging
Production
The current application environment is determined via the CRYSTAL_ENV variable.
Finally, you must know how to start the server.
Responses is mostly an Azu implementation detail to enable more type-safe definitions.
Response is mostly an Azu implementation detail to enable more type-safe definitions, and it does not represent the raw response from the HTTP::Server::Response. Instead responses are plain simple crystal objects that allows for easy implementation, validation and test-ability.
Azu::Responses main job is to render the body of the responses to be sent back to browsers and API clients.
Responses are created by including the Azu::Response and defining a render method that contains the body for the HTTP response.
Because of the simplicity of the module it is easy to create type safe responses, that can be validated and easily tested.
For example, lets say we want to render an IndexPage for a dashboard, we want to make ensure that a title is always provided in order to display the page.
Responses are simple Crystal classes that includes the Azu::Response module, there is no magic or macro needed, simply follows crystal conventions.
Rendering Inline HTML
Taking the example IndexPage above we can represent the H1 using Markup DSL
Rendering Templates
Azu::Response main job is to render the body of the responses to be sent back to browsers and API clients. Most of the time, we use templates to build said responses.
Templates work great for many reasons, some of those, easy to share with designers and front-end developers, portable across teams, and clear separation from presentation and back-end code.
To use templates include the Templates::Renderable module and use the render/2 method
Gets raw object from the raw
HTTP Server context. Allows access to the raw Request and objects
Azu follows the standards this is why Azu first attempts to load configuration values from the environment first if not found then it loads from code.
Azu provides a module to allow you write HTML in plain crystal
Configuration
Environment Variables
Default Value
port
PORT
4000
jj port_reuse
PORT_REUSE
true
host
HOST
"0.0.0.0"
log
CRYSTAL_LOG_LEVEL, CRYSTAL_LOG_SOURCES
Log.for("Azu")
env
CRYSTAL_ENV
Environment::Development
template.path
TEMPLATES_PATH
"./templates"
template.error_path
ERROR_TEMPLATE
"./error_template"
ssl_cert
SSL_CERT
""
ssl_key
SSL_KEY
""
ssl_ca
SSL_MODE
""
ssl_mode
SSL_CA
"none"
Validation in Azu is support by the Schema shards and is already included with every Crystal Application
Self validating Schemas are beneficial, and in my opinion, ideal, for when defining API Requests, Web Forms, JSON. Schema-Validation Takes a different approach and focuses a lot on explicitness, clarity, and precision of validation logic. It is designed to work with any data input, whether it’s a simple hash, an array or a complex object with deeply nested data.
Each validation is encapsulated by a simple, stateless predicate that receives some input and returns either true or false. Those predicates are encapsulated by rules which can be composed together using predicate logic, meaning you can use the familiar logic operators to build up a validation schema.
You can also perform validations for existing objects without the use of Schemas.
Simply create a class {Name}Validator
with the following signature:
You can define your custom predicates by simply creating a custom validator or creating methods in the Schema::Predicates
module ending with ?
and it should return a boolean
. For example:
The differences between a custom validator and a method predicate are:
Custom Validators
Must be inherited from Schema::Validator
abstract
Receives an instance of the object as a record
instance var.
Must have a :field
and :message
defined.
Must implement a def valid? : Array(Schema::Error)
method.
Predicates
Assertions of the property value against an expected value.
Predicates are light weight boolean methods.
Predicates methods must be defined as def {predicate}?(property_value, expected_value) : Bool
.
These are the current available predicates.
CONTRIBUTE - Add more predicates to this shards by contributing a Pull Request.
Azu uses Crinja a powerful template engine built for the Crystal Language
,Templates::Renderable will define a private function named render(template : String, data)
with one clause per file system template.
The following is a quick overview of the template language to get you started.
More details can be found in the template guide. The original Jinja2 template reference can also be helpful, Crinja templates are mostly similar.
In a template, expressions inside double curly braces ({{
... }}
) will be evaluated and printed to the template output.
Assuming there is a variable name
with value "World"
, the following template renders Hello, World!
.
The properties of an object can be accessed by a dot (.
) or square brackets ([]
). Filters modify the value of an expression.
Tests are similar to filters but are used in the context of a boolean expression, for example as a condition of an if
tag.
Tags control the logic of the template. They are enclosed in {%
and %}
.
The for
tag allows looping over a collection.
Other templates can be included using the include
tag:
Macros are similar to functions in other programming languages.
Template inheritance enables the use of block
tags in parent templates that can be overwritten by child templates. This is useful for implementing layouts:
Spark provides rich, real-time user experiences with server-rendered HTML
Spark will re-render the relevant parts of its HTML template and push it to the browser, which updates itself in the most efficient manner. This means developers write Spark templates as any other server-rendered HTML and Spark does the hard work of tracking changes and sending the relevant diffs to the browser.
At the end of the day, a Spark is nothing more than a process that receives events as messages and updates its state. The state itself is nothing more than functional and immutable Crystal data structures.
This architecture eliminates the complexity imposed by full-stack front-end frameworks without abandoning high-performance reactive user experiences. With Spark, small teams can do big things faster than ever before. We invite you to explore a fresh alternative to the Single Page App (SPA).
To enable Spark live rendering you must first mount the /live-view. In your main application crystal file simple add the following snippet.
With this snippet we define the the Server side Web Socket connection will be used to render all your spark components.
In order for Spark to communicate with the Browser we must define a Web Socket connection on the Client side. This is done by including the spark javascript.
Spark Components decompose response content into small independent contexts that can be lazily loaded.
Components are defined by including Azu::Component and are mounted/rendered by calling YourComponent.mount
in a parent Azu::Response. Components run inside the Response object, but may have their own state and event handling.
The most basic way to render a Spark component on a page is using the component mount method
You begin by rendering a Spark component typically from your Response class. A Spark component's mount
method gets called on the initial page load and every subsequent component update.
Example Azu Response rendering a Spark Component
A Spark component can be initialize with properties using the mount
method. For this to work simply define an initialize method with the name properties as you would normally do with any Crystal class.
The use of properties enables re-usability of the components create.
Mounting the component above would look like
Spark components comes with the refresh
and every
methods and allows you to define the refresh interval for a given component
With every Spark Component you must define a content method. The body of the method it's what gets rendered when the component is mounted to the page.
Spark components are meant to render dynamic content, at the same time components must be reusable and easy to maintain, for these reason Spark components have Markup, a lean html dsl written in Crystal to allow you write HTML natively.
Markup is intuitive to use and fast to render.
While you can use Markup to write your html code is not limited. You can use docstrings, templates or what suits your needs bests.
This chapter describes some particular attacks related to sessions, and security measures to protect your session data.
HTTP is a stateless protocol, and by default, HTTP requests are independent messages that don't retain user values. However, Session shard implements several approaches to bind and store user state data between requests.
Sessions enable the application to maintain user-specific state, while users interact with the application. For example, sessions allow users to authenticate once and remain signed in for future requests.
Azu offers strongly typed Session Management to manage application sessions and state.
Add the dependency to your shard.yml
:
Run shards install
The Session shard uses a store maintained by the app to persist data across requests from a client. The session data is backed by a cache and considered ephemeral data.
Recommendation: The site should continue to function without the session data. Critical application data should be stored in the user database and cached in session only as a performance optimization.
The Session shard ships with three forms of session storage out of the box; CookieStore, MemoryStore, and RedisStore.
The CookieStore is based on a Verifier and Encryptor, which encrypts and signs each cookie to ensure it can't be read or tampered with.
Since this store uses crypto features, you must set the secret
field in the configuration.
After the secret is defined, you can instantiate the CookieStore provider
The memory store uses server memory and is the default for the session configuration.
We don't recommend using this store in production. Every session will be stored in MEMORY, and the shard will not remove session entries upon expiration unless you create a task responsible for cleaning up expired entries.
Also, multiple servers cannot share the stored sessions.
The RedisStore is recommended for production use as it is highly scalable and is shareable across multiple processes.
The Session shard offers type-safe access to the values stored in the session, meaning that to store values in the session, you must first define the object.
To define a session data object
To write and read to and from the current_session
Note: Session also offers a HTTP Handler
Session::SessionHandler
to automatically enable session management for the Application. Each request that passes through the Session Handlers resets the timeout for the cookie
A very simple HTTP handler enables session management for an HTTP application that writes and reads session cookies.
Requests are designed by contract in order to enforce correctness and type-safe definitions
Every HTTP request message has a specific form:
An HTTP message is either a request from a client to a server or a response from a server to a client.
Requests are designed by contract in order to enforce correctness. What this means is that requests are strictly typed and can have preconditions. With this concept.
The Request provides concise, type-safe, and self-validated request objects.
Self-documented request objects.
Type-safe requests and parameters
Enables Focused and effective testing.
JSON body requests render object instances.
Azu::Contract are provided by tight integration with the Schema shard
Example Use:
Requests can be initialized are initialized in the background and property is available to the Endpoint of the same name of the request as the camel case.
Requests can be initialized from JSON, YAML, and the standard initializes method new.
Instance Method
Description
validate
A macro to define validation rules for your request
valid?
Validates the request object and returns true or false
validate!
Validates and raises an exception when invalid
rules
returns a list of rules to be applied on validation
params
to_json
A JSON representation of the request
to_yaml
A YAML representation of the request
When the built-in validation helpers are not enough for your needs, you can write your own validators or validation methods as you prefer.
Custom validators are simple classes that inherit from Schema::Validator
. These classes must implement the valid?
method which takes a record as an argument and performs the validation on it. The custom validator is called using the valid?
or validate!
method.
To enable the custom validator to your structs or classes by simply defining the use ConfirmPasswordValidator
Request params. See
Crystal I18n is an internationalization library for the Crystal programming language a unified interface allowing to leverage translations and localized contents in a Crystal project.
Simply add the following entry to your project's shard.yml
:
And run shards install
afterwards.
Assuming that a config/locales
relative folder exists in your project, with the following en.yml
file in it:
The following setup could be performed in order to initialize I18n
properly:
Here a translation loader is configured to load the previous translation file while also configuring the default locale (en
) and initializing the I18n
module.
Translations lookups can now be performed using the #translate
method (or the shorter version #t
) as follows:
Start sending and receiving emails from and to your Crystal application
Add this to your application's shard.yml
:
Templates go in the same folder the email is in:
Text email: <folder_email_class_is_in>/templates/<underscored_class_name>/text.ecr
HTML email: <folder_email_class_is_in>/templates/<underscored_class_name>/html.ecr
So if your email class is in src/emails/welcome_email.cr
, then your templates would go in src/emails/templates/welcome_email/text|html.ecr
.
Layouts are optional allowing you to specify how each email template looks individually. If you'd like to have the same layout on each, you can create a layout template in <folder_email_class_is_in>/templates/<layout_name>/layout.ecr
In this file, you'll yield the main email body with <%= content %>
. Then in your BaseEmail
, you can specify the name of the layout.
The built-in delay uses the deliver_later_strategy
setting set to Carbon::SpawnStrategy
. You can create your own custom delayed strategy that inherits from Carbon::DeliverLaterStrategy
and defines a run
method that takes a Carbon::Email
and a block.
One example might be a job processor:
Unit testing is simple. Instantiate your email and test the fields you care about.
Note that unit testing can be superfluous in most cases. Instead, try unit testing just fields that have complex logic. The compiler will catch most other issues.
Azu leverages the library for writing, sending, and testing emails. Carbon can be configured using the default file generated with a new Azu application in initializers/carbon.cr
. In that file, you can add SendGrid keys and change adapters.
Carbon::SendGridAdapter
- See .
Carbon::SmtpAdapter
- See .
Carbon::AwsSesAdapter
- See .
Carbon::SendInBlueAdapter
- See .
Carbon::MailgunAdapter
- See .
Carbon::SparkPostAdapter
- See .
Carbon::PostmarkAdapter
- See .
Carbon::MailersendAdapter
- See .
For more information on what you can do with Embedded Crystal (ECR), see .