{"id":21030968,"url":"https://github.com/azutoolkit/azu","last_synced_at":"2026-01-31T18:10:37.340Z","repository":{"id":40405060,"uuid":"236335471","full_name":"azutoolkit/azu","owner":"azutoolkit","description":"Application Development Toolkit for Crystal Language ","archived":false,"fork":false,"pushed_at":"2026-01-24T23:18:41.000Z","size":19464,"stargazers_count":25,"open_issues_count":2,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-01-25T12:54:02.892Z","etag":null,"topics":["application","artisan","craftmanship","crystal","development","toolkit","web"],"latest_commit_sha":null,"homepage":"https://azutopia.gitbook.io/azu/","language":"Crystal","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/azutoolkit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/contributing/roadmap.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-01-26T15:56:23.000Z","updated_at":"2026-01-24T23:16:12.000Z","dependencies_parsed_at":"2025-06-28T14:26:31.346Z","dependency_job_id":"6020d3be-08e8-43ea-a0bc-c50a2c5b7e74","html_url":"https://github.com/azutoolkit/azu","commit_stats":null,"previous_names":[],"tags_count":68,"template":false,"template_full_name":null,"purl":"pkg:github/azutoolkit/azu","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/azutoolkit%2Fazu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/azutoolkit%2Fazu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/azutoolkit%2Fazu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/azutoolkit%2Fazu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/azutoolkit","download_url":"https://codeload.github.com/azutoolkit/azu/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/azutoolkit%2Fazu/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28949274,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T14:26:55.697Z","status":"ssl_error","status_checked_at":"2026-01-31T14:26:52.545Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["application","artisan","craftmanship","crystal","development","toolkit","web"],"created_at":"2024-11-19T12:22:42.479Z","updated_at":"2026-01-31T18:10:37.321Z","avatar_url":"https://github.com/azutoolkit.png","language":"Crystal","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv style=\"text-align:center\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/azutoolkit/azu/master/azu.png\" /\u003e\u003c/div\u003e\n\n# Azu Web Framework\n\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/b58f03f01de241e0b75f222e31d905d7)](https://www.codacy.com/manual/eliasjpr/azu?utm_source=github.com\u0026utm_medium=referral\u0026utm_content=eliasjpr/azu\u0026utm_campaign=Badge_Grade) [![Crystal CI](https://github.com/azutoolkit/azu/actions/workflows/crystal.yml/badge.svg)](https://github.com/azutoolkit/azu/actions/workflows/crystal.yml) [![CodeQL](https://github.com/azutoolkit/azu/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/azutoolkit/azu/actions/workflows/github-code-scanning/codeql)\n\n**Azu** is a high-performance, type-safe web framework for Crystal that emphasizes developer productivity, compile-time safety, and real-time capabilities. Built with modern web development patterns, Azu provides a robust foundation for building scalable applications with elegant, expressive syntax.\n\n## Quick Start\n\n```crystal\nrequire \"azu\"\n\n# Define a type-safe endpoint\nstruct HelloEndpoint\n  include Azu::Endpoint\n\n  get \"/hello/:name\"\n\n  def call\n    response.body(\"Hello, #{request.param(\"name\")}!\")\n  end\nend\n\n# Start the server\nAzu.start\n```\n\n## Key Features\n\n- **Type-Safe**: Compile-time type checking for requests, responses, and parameters\n- **Real-Time**: WebSocket channels and live components with client-server synchronization\n- **Performance**: High-performance routing with LRU cache and path optimization\n- **Developer Experience**: Comprehensive error handling, content negotiation, and flexible middleware\n\n## 🚀 Key Features\n\n### Type-Safe Architecture\n\n- **Compile-time type checking** for requests, responses, and parameters\n- **Type-safe endpoint pattern** with structured request/response contracts\n- **Zero-runtime overhead** parameter validation and serialization\n\n### Real-Time Capabilities\n\n- **WebSocket channels** with automatic connection management\n- **Live components** with client-server synchronization\n- **Spark system** for reactive UI updates without page reloads\n\n### Performance-Optimized\n\n- **High-performance routing** with LRU cache and path optimization\n- **Template hot reloading** in development with production caching\n- **Efficient file upload handling** with streaming and size limits\n\n### Developer Experience\n\n- **Comprehensive error handling** with detailed debugging information\n- **Content negotiation** supporting JSON, HTML, XML, and plain text\n- **Flexible middleware system** for cross-cutting concerns\n- **Environment-aware configuration** with sensible defaults\n\n## 📊 Performance Characteristics\n\n- **Sub-millisecond routing** with cached path resolution\n- **Memory-efficient** request/response handling\n- **Concurrent WebSocket** connections with lightweight fiber-based architecture\n- **Zero-allocation** parameter parsing for common scenarios\n- **Template compilation** with optional hot-reloading for development\n\n## 🏗️ Architecture Overview\n\n```mermaid\ngraph TB\n    Client[Client Request] --\u003e Router[High-Performance Router]\n    Router --\u003e Endpoint[Type-Safe Endpoints]\n    Endpoint --\u003e Request[Request Contracts]\n    Endpoint --\u003e Response[Response Objects]\n\n    WS[WebSocket] --\u003e Channel[Channel Handlers]\n    Channel --\u003e Component[Live Components]\n    Component --\u003e Spark[Spark System]\n\n    Endpoint --\u003e Templates[Template Engine]\n    Templates --\u003e Hot[Hot Reload]\n\n    Router --\u003e Middleware[Middleware Stack]\n    Middleware --\u003e CORS[CORS]\n    Middleware --\u003e Auth[Authentication]\n    Middleware --\u003e Rate[Rate Limiting]\n\n    subgraph \"Type Safety\"\n        Request --\u003e Validation[Compile-time Validation]\n        Response --\u003e Serialization[Type-safe Serialization]\n    end\n```\n\n## 🛠️ Installation\n\n1. Add the dependency to your `shard.yml`:\n\n```yaml\ndependencies:\n  azu:\n    github: azutoolkit/azu\n    version: ~\u003e 0.4.14\n```\n\n2. Run `shards install`\n\n3. Require Azu in your application:\n\n```crystal\nrequire \"azu\"\n```\n\n## 🎯 Complete Quickstart Guide\n\nThis guide walks through building a complete Azu application with all major features in logical order.\n\n### 1. Project Configuration\n\nCreate your main application file with comprehensive configuration:\n\n```crystal\n# src/my_app.cr\nrequire \"azu\"\n\nmodule MyApp\n  include Azu\n\n  # Application configuration\n  configure do\n    # Server settings\n    port = ENV.fetch(\"PORT\", \"4000\").to_i\n    host = ENV.fetch(\"HOST\", \"0.0.0.0\")\n\n    # SSL/TLS configuration\n    ssl_cert = ENV[\"SSL_CERT\"]?\n    ssl_key = ENV[\"SSL_KEY\"]?\n\n    # Template configuration\n    templates.path = [\"src/templates\", \"src/views\"]\n    template_hot_reload = ENV.fetch(\"CRYSTAL_ENV\", \"development\") == \"development\"\n\n    # File upload configuration\n    upload.max_file_size = 50.megabytes\n    upload.temp_dir = \"tmp/uploads\"\n    upload.buffer_size = 8192\n\n    # Cache configuration\n    cache_config.enabled = true\n    cache_config.store = \"memory\"\n    cache_config.max_size = 1000\n    cache_config.default_ttl = 300\n\n    # Performance monitoring (optional)\n    performance_enabled = ENV.fetch(\"PERFORMANCE_MONITORING\", \"false\") == \"true\"\n\n    # Development tools\n    if env.development?\n      performance_profiling_enabled = true\n      performance_memory_monitoring = true\n    end\n  end\nend\n\n# Load application components\nrequire \"./models/*\"\nrequire \"./services/*\"\nrequire \"./requests/*\"\nrequire \"./responses/*\"\nrequire \"./validators/*\"\nrequire \"./endpoints/*\"\nrequire \"./channels/*\"\nrequire \"./middleware/*\"\n```\n\n### 2. Server Startup\n\nCreate a dedicated server file to start your application:\n\n```crystal\n# server.cr\nrequire \"./src/my_app\"\n\n# Build handler chain with explicit typing to avoid Crystal type inference issues\n{% if env(\"PERFORMANCE_MONITORING\") == \"true\" || flag?(:performance_monitoring) %}\n  MyApp.start [\n    Azu::Handler::RequestId.new,                     # Enhanced request ID tracking\n    Azu::Handler::DevDashboard.new,                  # Development Dashboard at /dev-dashboard\n    MyApp::CONFIG.performance_monitor.not_nil!,     # Performance metrics collection (shared instance)\n    Azu::Handler::Rescuer.new,                       # Enhanced error handling\n    Azu::Handler::Logger.new,                        # Request logging\n  ]\n{% else %}\n  MyApp.start [\n    Azu::Handler::RequestId.new, # Enhanced request ID tracking\n    Azu::Handler::Rescuer.new,   # Enhanced error handling\n    Azu::Handler::Logger.new,    # Request logging\n  ]\n{% end %}\n\nputs \"🚀 MyApp server starting on http://#{MyApp::CONFIG.host}:#{MyApp::CONFIG.port}\"\n```\n\nTo run your application:\n\n```bash\n# Development\ncrystal run server.cr\n\n# Production build\ncrystal build server.cr --release\n./server\n\n# With environment variables\nCRYSTAL_ENV=production PORT=8080 crystal run server.cr\n```\n\n### 3. Request Contracts with Validation\n\nDefine type-safe request objects with comprehensive validation:\n\n```crystal\n# src/requests/user_request.cr\nstruct UserRequest\n  include Azu::Request\n\n  getter name : String\n  getter email : String\n  getter age : Int32?\n  getter profile_image : Azu::Params::Multipart::File?\n\n  def initialize(@name = \"\", @email = \"\", @age = nil, @profile_image = nil)\n  end\n\n    # Built-in validation rules\n  validate :name, presence: true, length: {min: 2, max: 50}\n  validate :email, presence: true, format: /\\A[\\w+\\-.]+@[a-z\\d\\-]+(\\.[a-z\\d\\-]+)*\\.[a-z]+\\z/i\n  validate :age, numericality: {greater_than: 0, less_than: 150}, if: -\u003e{ age }\n\n  # Use custom validators (defined in section 4)\n  use EmailValidator\n  use UniqueRecordValidator\n  use AgeValidator\n  use FileValidator\nend\n\n# src/requests/search_request.cr\nstruct SearchRequest\n  include Azu::Request\n\n  getter query : String\n  getter page : Int32\n  getter per_page : Int32\n  getter sort : String\n  getter filters : Hash(String, String)\n\n  def initialize(@query = \"\", @page = 1, @per_page = 20, @sort = \"created_at\", @filters = {} of String =\u003e String)\n  end\n\n  validate :query, presence: true, length: {min: 1}\n  validate :page, numericality: {greater_than: 0}\n  validate :per_page, numericality: {greater_than: 0, less_than_or_equal_to: 100}\n  validate :sort, inclusion: {in: %w(name email created_at updated_at)}\nend\n```\n\n### 4. Custom Validators\n\nCreate reusable validation logic using Azu's validator pattern:\n\n```crystal\n# src/validators/email_validator.cr\nclass EmailValidator \u003c Azu::Validator\n  getter :record, :field, :message\n\n  EMAIL_REGEX = /\\A[\\w+\\-.]+@[a-z\\d\\-]+(\\.[a-z\\d\\-]+)*\\.[a-z]+\\z/i\n\n  def initialize(@record : User)\n    @field = :email\n    @message = \"Email must be valid!\"\n  end\n\n  def valid? : Array(Schema::Error)\n    errors = [] of Schema::Error\n\n    email = @record.email\n    return errors if email.empty?\n\n    unless EMAIL_REGEX.match(email)\n      errors \u003c\u003c Schema::Error.new(@field, @message)\n    end\n\n    errors\n  end\nend\n\n# src/validators/unique_record_validator.cr\nclass UniqueRecordValidator \u003c Azu::Validator\n  getter :record, :field, :message\n\n  def initialize(@record : User)\n    @field = :email\n    @message = \"Email must be unique!\"\n  end\n\n  def valid? : Array(Schema::Error)\n    errors = [] of Schema::Error\n\n    # Check if email already exists in database\n    if User.where(email: @record.email).where { id != @record.id }.exists?\n      errors \u003c\u003c Schema::Error.new(@field, @message)\n    end\n\n    errors\n  end\nend\n\n# src/validators/age_validator.cr\nclass AgeValidator \u003c Azu::Validator\n  getter :record, :field, :message\n\n  def initialize(@record : User)\n    @field = :age\n    @message = \"Age must be between 13 and 120!\"\n  end\n\n  def valid? : Array(Schema::Error)\n    errors = [] of Schema::Error\n\n    age = @record.age\n    return errors unless age # Skip if age is nil\n\n    unless (13..120).includes?(age)\n      errors \u003c\u003c Schema::Error.new(@field, @message)\n    end\n\n    errors\n  end\nend\n\n# src/validators/file_validator.cr\nclass FileValidator \u003c Azu::Validator\n  getter :record, :field, :message\n\n  IMAGE_EXTENSIONS = %w(.jpg .jpeg .png .gif .webp)\n\n  def initialize(@record : User)\n    @field = :profile_image\n    @message = \"Profile image must be a valid image file!\"\n  end\n\n  def valid? : Array(Schema::Error)\n    errors = [] of Schema::Error\n\n    # This assumes your User model has a profile_image_path field\n    return errors unless @record.profile_image_url\n\n    extension = File.extname(@record.profile_image_url || \"\").downcase\n    unless IMAGE_EXTENSIONS.includes?(extension)\n      errors \u003c\u003c Schema::Error.new(@field, @message)\n    end\n\n    errors\n  end\nend\n```\n\n**Using Custom Validators in Models:**\n\n```crystal\n# src/models/user.cr\nstruct User\n  include Azu::Request\n\n  property id : Int64?\n  property name : String\n  property email : String\n  property age : Int32?\n  property profile_image_url : String?\n\n  def initialize(@name = \"\", @email = \"\", @age = nil, @profile_image_url = nil)\n  end\n\n  # Use custom validators\n  use EmailValidator\n  use UniqueRecordValidator\n  use AgeValidator\n  use FileValidator\n\n  # Built-in validations still work alongside custom validators\n  validate :name, presence: true, length: {min: 2, max: 50}\nend\n```\n\n**Advanced Custom Validator Example:**\n\n```crystal\n# src/validators/password_strength_validator.cr\nclass PasswordStrengthValidator \u003c Azu::Validator\n  getter :record, :field, :message\n\n  def initialize(@record : User)\n    @field = :password\n    @message = \"Password must contain at least 8 characters, one uppercase, one lowercase, and one number!\"\n  end\n\n  def valid? : Array(Schema::Error)\n    errors = [] of Schema::Error\n\n    password = @record.password\n    return errors if password.empty?\n\n    # Check length\n    if password.size \u003c 8\n      errors \u003c\u003c Schema::Error.new(@field, \"Password must be at least 8 characters long!\")\n      return errors\n    end\n\n    # Check for uppercase letter\n    unless password.match(/[A-Z]/)\n      errors \u003c\u003c Schema::Error.new(@field, \"Password must contain at least one uppercase letter!\")\n    end\n\n    # Check for lowercase letter\n    unless password.match(/[a-z]/)\n      errors \u003c\u003c Schema::Error.new(@field, \"Password must contain at least one lowercase letter!\")\n    end\n\n    # Check for number\n    unless password.match(/[0-9]/)\n      errors \u003c\u003c Schema::Error.new(@field, \"Password must contain at least one number!\")\n    end\n\n    # Check for special character (optional)\n    unless password.match(/[!@#$%^\u0026*(),.?\":{}|\u003c\u003e]/)\n      errors \u003c\u003c Schema::Error.new(@field, \"Password should contain at least one special character!\")\n    end\n\n    errors\n  end\nend\n```\n\n### 5. Data Models\n\nDefine your data models using [CQL ORM](https://github.com/azutoolkit/cql):\n\n```crystal\nrequire \"cql\"\n\nstruct User\n  include CQL::ActiveRecord::Model(Int64)\n  db_context AppDB, :users\n\n  getter id : Int64?\n  getter name : String\n  getter email : String\n\n  has_many :posts, Post, foreign_key: :user_id\n  validates :name, presence: true, size: 2..50\n  validates :email, presence: true\n\n  scope :active, -\u003e { where(active: true) }\nend\n```\n\nSee the [Database documentation](docs/database/cql-overview.md) for complete CQL integration details including schemas, migrations, and queries.\n\n### 6. Services (Business Logic)\n\nOrganize business logic in service classes:\n\n```crystal\nmodule UserService\n  extend self\n\n  def create_user(request : UserRequest) : User\n    raise Azu::Response::ValidationError.new(request.errors) unless request.valid?\n\n    user = User.create!(\n      name: request.name,\n      email: request.email\n    )\n\n    user\n  end\n\n  def find_user(id : Int64) : User?\n    User.find?(id)\n  end\nend\n```\n\n### 7. Response Objects\n\nCreate structured response objects:\n\n```crystal\n# src/responses/user_response.cr\nstruct UserResponse\n  include Azu::Response\n  include JSON::Serializable\n\n  getter id : Int64\n  getter name : String\n  getter email : String\n  getter age : Int32?\n  getter profile_image_url : String?\n  getter created_at : String\n\n  def initialize(user : User)\n    @id = user.id\n    @name = user.name\n    @email = user.email\n    @age = user.age\n    @profile_image_url = user.profile_image_url\n    @created_at = user.created_at.to_s(\"%Y-%m-%d %H:%M:%S\")\n  end\n\n  def render\n    to_json\n  end\nend\n\n# src/responses/user_list_response.cr\nstruct UserListResponse\n  include Azu::Response\n  include JSON::Serializable\n\n  getter users : Array(UserResponse)\n  getter pagination : PaginationResponse\n\n  def initialize(@users : Array(UserResponse), @pagination : PaginationResponse)\n  end\n\n  def render\n    to_json\n  end\nend\n\n# src/responses/pagination_response.cr\nstruct PaginationResponse\n  include JSON::Serializable\n\n  getter page : Int32\n  getter per_page : Int32\n  getter total : Int32\n  getter total_pages : Int32\n\n  def initialize(@page : Int32, @per_page : Int32, @total : Int32)\n    @total_pages = (@total.to_f / @per_page.to_f).ceil.to_i\n  end\nend\n\n# src/responses/user_page_response.cr\nstruct UserPageResponse\n  include Azu::Response\n  include Azu::Templates::Renderable\n\n  getter user : User\n  getter posts : Array(Post)\n\n  def initialize(@user : User, @posts : Array(Post))\n  end\n\n  def render\n    view \"users/show.html\", {\n      \"user\" =\u003e {\n        \"id\" =\u003e user.id,\n        \"name\" =\u003e user.name,\n        \"email\" =\u003e user.email,\n        \"profile_image_url\" =\u003e user.profile_image_url\n      },\n      \"posts\" =\u003e posts.map { |post|\n        {\n          \"id\" =\u003e post.id,\n          \"title\" =\u003e post.title,\n          \"content\" =\u003e post.content,\n          \"created_at\" =\u003e post.created_at.to_s(\"%B %d, %Y\")\n        }\n      },\n      \"title\" =\u003e \"#{user.name}'s Profile\"\n    }\n  end\nend\n```\n\n### 8. Type-Safe Endpoints\n\nCreate endpoints that connect everything together:\n\n```crystal\n# src/endpoints/user_endpoints.cr\nstruct CreateUserEndpoint\n  include Azu::Endpoint(UserRequest, UserResponse)\n\n  post \"/api/users\"\n\n  def call : UserResponse\n    user = UserService.create_user(request)\n    UserResponse.new(user)\n  rescue ex : Azu::Response::ValidationError\n    raise ex\n  rescue ex\n    raise Azu::Response::InternalServerError.new(\"Failed to create user: #{ex.message}\")\n  end\nend\n\nstruct GetUserEndpoint\n  include Azu::Endpoint(EmptyRequest, UserResponse)\n\n  get \"/api/users/:id\"\n\n  def call : UserResponse\n    user_id = path_params[\"id\"].to_i64\n    user = UserService.find_user(user_id)\n\n    unless user\n      raise Azu::Response::NotFoundError.new(\"User not found\")\n    end\n\n    UserResponse.new(user)\n  end\nend\n\nstruct SearchUsersEndpoint\n  include Azu::Endpoint(SearchRequest, UserListResponse)\n\n  get \"/api/users\"\n\n  def call : UserListResponse\n    users = UserService.search_users(request)\n    user_responses = users.map { |user| UserResponse.new(user) }\n\n    # Get total count for pagination\n    total = User.count\n    pagination = PaginationResponse.new(request.page, request.per_page, total)\n\n    UserListResponse.new(user_responses, pagination)\n  end\nend\n\nstruct UserProfilePageEndpoint\n  include Azu::Endpoint(EmptyRequest, UserPageResponse)\n\n  get \"/users/:id\"\n\n  def call : UserPageResponse\n    user_id = path_params[\"id\"].to_i64\n    user = UserService.find_user(user_id)\n\n    unless user\n      raise Azu::Response::NotFoundError.new(\"User not found\")\n    end\n\n    posts = Post.by_user(user_id).published.recent.limit(10).to_a\n    UserPageResponse.new(user, posts)\n  end\nend\n\n# File upload endpoint\nstruct UploadAvatarEndpoint\n  include Azu::Endpoint(FileUploadRequest, FileUploadResponse)\n\n  post \"/api/users/:id/avatar\"\n\n  def call : FileUploadResponse\n    user_id = path_params[\"id\"].to_i64\n    user = UserService.find_user(user_id)\n\n    unless user\n      raise Azu::Response::NotFoundError.new(\"User not found\")\n    end\n\n    # Validate file\n    unless request.file\n      raise Azu::Response::ValidationError.new(\"file\", \"Avatar file is required\")\n    end\n\n    # Upload and update user\n    avatar_url = FileUploadService.upload_image(request.file.not_nil!)\n    user.profile_image_url = avatar_url\n    user.save!\n\n    FileUploadResponse.new(\n      filename: File.basename(avatar_url),\n      url: avatar_url,\n      size: request.file.not_nil!.size\n    )\n  end\nend\n```\n\n### 9. Crinja Templates with Layouts\n\nCreate template layouts and pages:\n\n```html\n\u003c!-- src/templates/layouts/base.html --\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /\u003e\n    \u003ctitle\u003e{% block title %}My App{% endblock %}\u003c/title\u003e\n    \u003clink\n      href=\"https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css\"\n      rel=\"stylesheet\"\n    /\u003e\n    {% block head %}{% endblock %}\n  \u003c/head\u003e\n  \u003cbody class=\"bg-gray-100\"\u003e\n    \u003cnav class=\"bg-blue-600 text-white p-4\"\u003e\n      \u003cdiv class=\"container mx-auto flex justify-between items-center\"\u003e\n        \u003ch1 class=\"text-xl font-bold\"\u003e\n          \u003ca href=\"/\" class=\"hover:text-blue-200\"\u003eMy App\u003c/a\u003e\n        \u003c/h1\u003e\n        \u003cdiv class=\"space-x-4\"\u003e\n          \u003ca href=\"/users\" class=\"hover:text-blue-200\"\u003eUsers\u003c/a\u003e\n          \u003ca href=\"/posts\" class=\"hover:text-blue-200\"\u003ePosts\u003c/a\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/nav\u003e\n\n    \u003cmain class=\"container mx-auto py-8\"\u003e\n      {% if flash_message %}\n      \u003cdiv\n        class=\"bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4\"\n      \u003e\n        {{ flash_message }}\n      \u003c/div\u003e\n      {% endif %} {% block content %}{% endblock %}\n    \u003c/main\u003e\n\n    \u003cfooter class=\"bg-gray-800 text-white p-4 mt-8\"\u003e\n      \u003cdiv class=\"container mx-auto text-center\"\u003e\n        \u003cp\u003e\u0026copy; 2024 My App. Built with Azu Framework.\u003c/p\u003e\n      \u003c/div\u003e\n    \u003c/footer\u003e\n\n    {% block scripts %}{% endblock %}\n  \u003c/body\u003e\n\u003c/html\u003e\n\n\u003c!-- src/templates/users/show.html --\u003e\n{% extends \"layouts/base.html\" %} {% block title %}{{ user.name }}'s Profile -\nMy App{% endblock %} {% block content %}\n\u003cdiv class=\"bg-white rounded-lg shadow-md overflow-hidden\"\u003e\n  \u003cdiv class=\"bg-gradient-to-r from-blue-500 to-purple-600 p-6 text-white\"\u003e\n    \u003cdiv class=\"flex items-center space-x-4\"\u003e\n      {% if user.profile_image_url %}\n      \u003cimg\n        src=\"{{ user.profile_image_url }}\"\n        alt=\"{{ user.name }}\"\n        class=\"w-20 h-20 rounded-full border-4 border-white\"\n      /\u003e\n      {% else %}\n      \u003cdiv\n        class=\"w-20 h-20 rounded-full bg-gray-300 flex items-center justify-center border-4 border-white\"\n      \u003e\n        \u003cspan class=\"text-2xl text-gray-600\"\u003e{{ user.name[0] | upper }}\u003c/span\u003e\n      \u003c/div\u003e\n      {% endif %}\n\n      \u003cdiv\u003e\n        \u003ch1 class=\"text-3xl font-bold\"\u003e{{ user.name }}\u003c/h1\u003e\n        \u003cp class=\"text-blue-100\"\u003e{{ user.email }}\u003c/p\u003e\n        {% if user.age %}\n        \u003cp class=\"text-sm text-blue-200\"\u003eAge: {{ user.age }}\u003c/p\u003e\n        {% endif %}\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003cdiv class=\"p-6\"\u003e\n    \u003ch2 class=\"text-2xl font-semibold mb-4\"\u003eRecent Posts\u003c/h2\u003e\n\n    {% if posts %}\n    \u003cdiv class=\"space-y-4\"\u003e\n      {% for post in posts %}\n      \u003carticle class=\"border-l-4 border-blue-500 pl-4 py-2\"\u003e\n        \u003ch3 class=\"text-lg font-medium text-gray-900\"\u003e\n          \u003ca href=\"/posts/{{ post.id }}\" class=\"hover:text-blue-600\"\u003e\n            {{ post.title }}\n          \u003c/a\u003e\n        \u003c/h3\u003e\n        \u003cp class=\"text-gray-600 mt-1\"\u003e{{ post.content | truncate(150) }}\u003c/p\u003e\n        \u003cp class=\"text-sm text-gray-500 mt-2\"\u003e{{ post.created_at }}\u003c/p\u003e\n      \u003c/article\u003e\n      {% endfor %}\n    \u003c/div\u003e\n    {% else %}\n    \u003cp class=\"text-gray-600\"\u003eNo posts yet.\u003c/p\u003e\n    {% endif %}\n  \u003c/div\u003e\n\u003c/div\u003e\n{% endblock %}\n\n\u003c!-- src/templates/users/index.html --\u003e\n{% extends \"layouts/base.html\" %} {% block title %}Users - My App{% endblock %}\n{% block content %}\n\u003cdiv class=\"flex justify-between items-center mb-6\"\u003e\n  \u003ch1 class=\"text-3xl font-bold\"\u003eUsers\u003c/h1\u003e\n  \u003ca\n    href=\"/users/new\"\n    class=\"bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition\"\n  \u003e\n    Add New User\n  \u003c/a\u003e\n\u003c/div\u003e\n\n\u003cdiv class=\"bg-white rounded-lg shadow-md overflow-hidden\"\u003e\n  \u003cdiv class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-6\"\u003e\n    {% for user in users %}\n    \u003cdiv class=\"border rounded-lg p-4 hover:shadow-lg transition\"\u003e\n      \u003cdiv class=\"flex items-center space-x-3 mb-3\"\u003e\n        {% if user.profile_image_url %}\n        \u003cimg\n          src=\"{{ user.profile_image_url }}\"\n          alt=\"{{ user.name }}\"\n          class=\"w-12 h-12 rounded-full\"\n        /\u003e\n        {% else %}\n        \u003cdiv\n          class=\"w-12 h-12 rounded-full bg-blue-500 flex items-center justify-center\"\n        \u003e\n          \u003cspan class=\"text-white font-semibold\"\n            \u003e{{ user.name[0] | upper }}\u003c/span\n          \u003e\n        \u003c/div\u003e\n        {% endif %}\n\n        \u003cdiv\u003e\n          \u003ch3 class=\"font-semibold text-gray-900\"\u003e{{ user.name }}\u003c/h3\u003e\n          \u003cp class=\"text-sm text-gray-600\"\u003e{{ user.email }}\u003c/p\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n\n      \u003cdiv class=\"flex justify-between items-center\"\u003e\n        \u003cspan class=\"text-sm text-gray-500\"\u003e{{ user.created_at }}\u003c/span\u003e\n        \u003ca\n          href=\"/users/{{ user.id }}\"\n          class=\"text-blue-600 hover:text-blue-800 font-medium\"\n        \u003e\n          View Profile\n        \u003c/a\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n    {% endfor %}\n  \u003c/div\u003e\n\u003c/div\u003e\n{% endblock %}\n```\n\n### 10. Live Components\n\nCreate interactive components with real-time updates:\n\n```crystal\n# src/components/user_counter_component.cr\nclass UserCounterComponent\n  include Azu::Component\n\n  property count : Int32 = 0\n  property user_id : Int64\n\n  def initialize(@user_id : Int64)\n    @count = User.count.to_i\n  end\n\n  def content\n    div(class: \"bg-white p-4 rounded-lg shadow-md\") do\n      h3(class: \"text-lg font-semibold mb-2\") { text \"Total Users\" }\n      div(class: \"flex items-center justify-between\") do\n        span(class: \"text-3xl font-bold text-blue-600\") { text @count.to_s }\n        button(\n          class: \"bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded\",\n          onclick: \"refresh_count\"\n        ) { text \"Refresh\" }\n      end\n    end\n  end\n\n  def on_event(name, data)\n    case name\n    when \"refresh_count\"\n      @count = User.count.to_i\n      refresh # Automatically updates the client\n    end\n  end\nend\n\n# src/components/chat_component.cr\nclass ChatComponent\n  include Azu::Component\n\n  property messages : Array(Hash(String, String)) = [] of Hash(String, String)\n  property current_user : String = \"Anonymous\"\n\n  def initialize(@current_user : String)\n  end\n\n  def content\n    div(class: \"bg-white rounded-lg shadow-md\") do\n      div(class: \"p-4 border-b\") do\n        h3(class: \"text-lg font-semibold\") { text \"Live Chat\" }\n      end\n\n      div(class: \"h-64 overflow-y-auto p-4 space-y-2\") do\n        @messages.each do |message|\n          div(class: \"flex space-x-2\") do\n            span(class: \"font-semibold text-blue-600\") { text \"#{message[\"user\"]}:\" }\n            span { text message[\"text\"] }\n          end\n        end\n      end\n\n      div(class: \"p-4 border-t\") do\n        form(onsubmit: \"send_message\", class: \"flex space-x-2\") do\n          input(\n            type: \"text\",\n            name: \"message\",\n            placeholder: \"Type a message...\",\n            class: \"flex-1 px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500\"\n          )\n          button(\n            type: \"submit\",\n            class: \"bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg\"\n          ) { text \"Send\" }\n        end\n      end\n    end\n  end\n\n  def on_event(name, data)\n    case name\n    when \"send_message\"\n      if message_text = data[\"message\"]?.try(\u0026.as_s)\n        unless message_text.empty?\n          @messages \u003c\u003c {\n            \"user\" =\u003e @current_user,\n            \"text\" =\u003e message_text,\n            \"timestamp\" =\u003e Time.utc.to_s\n          }\n          refresh\n\n          # Broadcast to other connected clients\n          broadcast(\"new_message\", {\n            \"user\" =\u003e @current_user,\n            \"text\" =\u003e message_text\n          })\n        end\n      end\n    when \"receive_message\"\n      if user = data[\"user\"]?.try(\u0026.as_s)\n        if text = data[\"text\"]?.try(\u0026.as_s)\n          @messages \u003c\u003c {\n            \"user\" =\u003e user,\n            \"text\" =\u003e text,\n            \"timestamp\" =\u003e Time.utc.to_s\n          }\n          refresh\n        end\n      end\n    end\n  end\nend\n```\n\n### 11. WebSocket Channels\n\nCreate real-time communication channels:\n\n```crystal\n# src/channels/chat_channel.cr\nclass ChatChannel \u003c Azu::Channel\n  SUBSCRIBERS = [] of HTTP::WebSocket\n\n  ws \"/chat\"\n\n  def on_connect\n    SUBSCRIBERS \u003c\u003c socket.not_nil!\n    user_count = SUBSCRIBERS.size\n\n    # Notify all clients about new user\n    broadcast_to_all(\"user_joined\", {\n      \"message\" =\u003e \"A user joined the chat\",\n      \"user_count\" =\u003e user_count\n    })\n\n    # Send current user count to new connection\n    send_to_socket(\"connection_established\", {\n      \"message\" =\u003e \"Welcome to the chat!\",\n      \"user_count\" =\u003e user_count\n    })\n  end\n\n  def on_message(message : String)\n    begin\n      data = JSON.parse(message)\n      event_type = data[\"type\"]?.try(\u0026.as_s) || \"message\"\n\n      case event_type\n      when \"chat_message\"\n        handle_chat_message(data)\n      when \"typing_start\"\n        handle_typing_start(data)\n      when \"typing_stop\"\n        handle_typing_stop(data)\n      else\n        send_error(\"Unknown message type: #{event_type}\")\n      end\n    rescue JSON::ParseError\n      send_error(\"Invalid JSON format\")\n    rescue ex\n      send_error(\"Error processing message: #{ex.message}\")\n    end\n  end\n\n  def on_close(code, message)\n    SUBSCRIBERS.delete(socket)\n    user_count = SUBSCRIBERS.size\n\n    # Notify remaining users\n    broadcast_to_all(\"user_left\", {\n      \"message\" =\u003e \"A user left the chat\",\n      \"user_count\" =\u003e user_count\n    })\n  end\n\n  private def handle_chat_message(data)\n    username = data[\"username\"]?.try(\u0026.as_s) || \"Anonymous\"\n    text = data[\"text\"]?.try(\u0026.as_s) || \"\"\n\n    return if text.empty?\n\n    # Validate message length\n    if text.size \u003e 500\n      send_error(\"Message too long (max 500 characters)\")\n      return\n    end\n\n    # Broadcast message to all subscribers\n    message_data = {\n      \"type\" =\u003e \"chat_message\",\n      \"username\" =\u003e username,\n      \"text\" =\u003e text,\n      \"timestamp\" =\u003e Time.utc.to_unix_ms\n    }\n\n    broadcast_to_all(\"new_message\", message_data)\n  end\n\n  private def handle_typing_start(data)\n    username = data[\"username\"]?.try(\u0026.as_s) || \"Anonymous\"\n\n    broadcast_to_others(\"user_typing\", {\n      \"username\" =\u003e username,\n      \"typing\" =\u003e true\n    })\n  end\n\n  private def handle_typing_stop(data)\n    username = data[\"username\"]?.try(\u0026.as_s) || \"Anonymous\"\n\n    broadcast_to_others(\"user_typing\", {\n      \"username\" =\u003e username,\n      \"typing\" =\u003e false\n    })\n  end\n\n  private def broadcast_to_all(event : String, data)\n    message = {\n      \"event\" =\u003e event,\n      \"data\" =\u003e data\n    }.to_json\n\n    SUBSCRIBERS.each do |subscriber|\n      begin\n        subscriber.send(message)\n      rescue ex\n        # Remove disconnected subscribers\n        SUBSCRIBERS.delete(subscriber)\n      end\n    end\n  end\n\n  private def broadcast_to_others(event : String, data)\n    message = {\n      \"event\" =\u003e event,\n      \"data\" =\u003e data\n    }.to_json\n\n    SUBSCRIBERS.each do |subscriber|\n      next if subscriber == socket # Skip current socket\n\n      begin\n        subscriber.send(message)\n      rescue ex\n        SUBSCRIBERS.delete(subscriber)\n      end\n    end\n  end\n\n  private def send_to_socket(event : String, data)\n    message = {\n      \"event\" =\u003e event,\n      \"data\" =\u003e data\n    }.to_json\n\n    socket.not_nil!.send(message)\n  rescue ex\n    # Handle send error\n  end\n\n  private def send_error(error_message : String)\n    send_to_socket(\"error\", {\"message\" =\u003e error_message})\n  end\nend\n\n# src/channels/notification_channel.cr\nclass NotificationChannel \u003c Azu::Channel\n  @@user_sockets = {} of String =\u003e HTTP::WebSocket\n\n  ws \"/notifications/:user_id\"\n\n  def on_connect\n    user_id = path_params[\"user_id\"]\n    @@user_sockets[user_id] = socket.not_nil!\n\n    # Send any pending notifications\n    send_pending_notifications(user_id)\n  end\n\n  def on_message(message : String)\n    # Handle ping/pong for connection keep-alive\n    if message == \"ping\"\n      socket.not_nil!.send(\"pong\")\n    end\n  end\n\n  def on_close(code, message)\n    # Remove user socket from tracking\n    user_id = path_params[\"user_id\"]\n    @@user_sockets.delete(user_id)\n  end\n\n  # Class method to send notification to specific user\n  def self.notify_user(user_id : String, notification : Hash(String, String | Int32))\n    if socket = @@user_sockets[user_id]?\n      begin\n        message = {\n          \"type\" =\u003e \"notification\",\n          \"data\" =\u003e notification\n        }.to_json\n\n        socket.send(message)\n      rescue ex\n        # Remove disconnected socket\n        @@user_sockets.delete(user_id)\n      end\n    end\n  end\n\n  private def send_pending_notifications(user_id : String)\n    # This would typically fetch from a database\n    # For demo purposes, sending a welcome notification\n    notification = {\n      \"id\" =\u003e 1,\n      \"title\" =\u003e \"Welcome!\",\n      \"message\" =\u003e \"You are now connected to real-time notifications\",\n      \"type\" =\u003e \"info\"\n    }\n\n    send_to_socket(\"notification\", notification)\n  end\nend\n```\n\n### 12. Custom Middleware\n\nCreate middleware for cross-cutting concerns:\n\n```crystal\n# src/middleware/authentication_middleware.cr\nclass AuthenticationMiddleware\n  include HTTP::Handler\n\n  def call(context)\n    # Skip authentication for certain paths\n    if public_path?(context.request.path)\n      call_next(context)\n      return\n    end\n\n    # Check for authentication token\n    token = extract_token(context.request)\n\n    unless token \u0026\u0026 valid_token?(token)\n      context.response.status = HTTP::Status::UNAUTHORIZED\n      context.response.content_type = \"application/json\"\n      context.response.print({\n        \"error\" =\u003e \"Authentication required\",\n        \"message\" =\u003e \"Please provide a valid authentication token\"\n      }.to_json)\n      return\n    end\n\n    # Add user information to context\n    if user = get_user_from_token(token)\n      context.set(\"current_user\", user)\n    end\n\n    call_next(context)\n  end\n\n  private def public_path?(path : String) : Bool\n    public_paths = [\"/\", \"/login\", \"/register\", \"/health\", \"/assets\"]\n    public_paths.any? { |public_path| path.starts_with?(public_path) }\n  end\n\n  private def extract_token(request : HTTP::Request) : String?\n    # Try Authorization header first\n    if auth_header = request.headers[\"Authorization\"]?\n      if auth_header.starts_with?(\"Bearer \")\n        return auth_header[7..-1]\n      end\n    end\n\n    # Try query parameter\n    request.query_params[\"token\"]?\n  end\n\n  private def valid_token?(token : String) : Bool\n    # Implement your token validation logic\n    # This could check JWT, database tokens, etc.\n    token.size \u003e 10 # Simple validation for demo\n  end\n\n  private def get_user_from_token(token : String) : User?\n    # Implement user lookup from token\n    # This would typically decode JWT or query database\n    User.first? # Simple demo\n  end\nend\n\n# src/middleware/cors_middleware.cr\nclass CustomCorsMiddleware\n  include HTTP::Handler\n\n  def initialize(@allowed_origins : Array(String) = [\"*\"],\n                 @allowed_methods : Array(String) = [\"GET\", \"POST\", \"PUT\", \"DELETE\", \"OPTIONS\"],\n                 @allowed_headers : Array(String) = [\"Content-Type\", \"Authorization\"],\n                 @max_age : Int32 = 86400)\n  end\n\n  def call(context)\n    request = context.request\n    response = context.response\n\n    # Set CORS headers\n    if origin = request.headers[\"Origin\"]?\n      if @allowed_origins.includes?(\"*\") || @allowed_origins.includes?(origin)\n        response.headers[\"Access-Control-Allow-Origin\"] = origin\n      end\n    end\n\n    response.headers[\"Access-Control-Allow-Methods\"] = @allowed_methods.join(\", \")\n    response.headers[\"Access-Control-Allow-Headers\"] = @allowed_headers.join(\", \")\n    response.headers[\"Access-Control-Max-Age\"] = @max_age.to_s\n    response.headers[\"Access-Control-Allow-Credentials\"] = \"true\"\n\n    # Handle preflight requests\n    if request.method == \"OPTIONS\"\n      response.status = HTTP::Status::NO_CONTENT\n      return\n    end\n\n    call_next(context)\n  end\nend\n\n# src/middleware/rate_limiting_middleware.cr\nclass RateLimitingMiddleware\n  include HTTP::Handler\n\n  @@request_counts = {} of String =\u003e {count: Int32, reset_time: Time}\n\n  def initialize(@requests_per_minute : Int32 = 60)\n  end\n\n  def call(context)\n    client_ip = get_client_ip(context.request)\n    current_time = Time.utc\n\n    # Clean old entries\n    cleanup_old_entries(current_time)\n\n    # Check rate limit\n    if rate_limited?(client_ip, current_time)\n      context.response.status = HTTP::Status::TOO_MANY_REQUESTS\n      context.response.content_type = \"application/json\"\n      context.response.headers[\"Retry-After\"] = \"60\"\n      context.response.print({\n        \"error\" =\u003e \"Rate limit exceeded\",\n        \"message\" =\u003e \"Too many requests. Please try again later.\"\n      }.to_json)\n      return\n    end\n\n    # Increment counter\n    increment_counter(client_ip, current_time)\n\n    call_next(context)\n  end\n\n  private def get_client_ip(request : HTTP::Request) : String\n    # Check for forwarded IP headers\n    if forwarded_for = request.headers[\"X-Forwarded-For\"]?\n      return forwarded_for.split(\",\").first.strip\n    end\n\n    if real_ip = request.headers[\"X-Real-IP\"]?\n      return real_ip\n    end\n\n    # Fall back to remote address\n    request.remote_address.try(\u0026.to_s) || \"unknown\"\n  end\n\n  private def rate_limited?(client_ip : String, current_time : Time) : Bool\n    if entry = @@request_counts[client_ip]?\n      # Reset counter if minute has passed\n      if current_time \u003e= entry[:reset_time]\n        return false\n      end\n\n      # Check if limit exceeded\n      return entry[:count] \u003e= @requests_per_minute\n    end\n\n    false\n  end\n\n  private def increment_counter(client_ip : String, current_time : Time)\n    if entry = @@request_counts[client_ip]?\n      if current_time \u003e= entry[:reset_time]\n        # Reset counter\n        @@request_counts[client_ip] = {\n          count: 1,\n          reset_time: current_time + 1.minute\n        }\n      else\n        # Increment counter\n        @@request_counts[client_ip] = {\n          count: entry[:count] + 1,\n          reset_time: entry[:reset_time]\n        }\n      end\n    else\n      # Create new entry\n      @@request_counts[client_ip] = {\n        count: 1,\n        reset_time: current_time + 1.minute\n      }\n    end\n  end\n\n  private def cleanup_old_entries(current_time : Time)\n    @@request_counts.select! do |ip, entry|\n      current_time \u003c entry[:reset_time]\n    end\n  end\nend\n```\n\n### 13. Background Jobs with JoobQ\n\nImplement asynchronous job processing for long-running tasks:\n\n**Add JoobQ to your `shard.yml`:**\n\n```yaml\ndependencies:\n  azu:\n    github: azutoolkit/azu\n    version: ~\u003e 0.4.14\n  joobq:\n    github: azutoolkit/joobq\n    version: ~\u003e 1.0.0\n```\n\n**Job Examples:**\n\n```crystal\n# src/jobs/email_job.cr\nstruct EmailJob\n  include JoobQ::Job\n\n  # Name of the queue to be processed by\n  @queue   = \"default\"\n  # Number Of Retries for this job\n  @retries = 3\n  # Job Expiration\n  @expires = 1.days.total_seconds.to_i\n\n  def initialize(@email_address : String, @subject : String, @body : String)\n  end\n\n  def perform\n    # Logic to send email\n    EmailService.send_email(@email_address, @subject, @body)\n    Log.info { \"Email sent to #{@email_address}\" }\n  rescue ex\n    Log.error(exception: ex) { \"Failed to send email to #{@email_address}\" }\n    raise ex # Let JoobQ handle retries\n  end\nend\n\n# src/jobs/image_processing_job.cr\nstruct ImageProcessingJob\n  include JoobQ::Job\n\n  @queue   = \"images\"\n  @retries = 2\n  @expires = 30.minutes.total_seconds.to_i\n\n  def initialize(@user_id : Int64, @image_path : String, @sizes : Array(String))\n  end\n\n  def perform\n    user = User.find(@user_id)\n    return unless user\n\n    @sizes.each do |size|\n      # Process image for different sizes (thumbnail, medium, large)\n      processed_path = ImageProcessor.resize(@image_path, size)\n\n      # Update user's image URLs\n      case size\n      when \"thumbnail\"\n        user.thumbnail_url = processed_path\n      when \"medium\"\n        user.medium_image_url = processed_path\n      when \"large\"\n        user.large_image_url = processed_path\n      end\n    end\n\n    user.save!\n    Log.info { \"Processed images for user #{@user_id}\" }\n  end\nend\n\n# src/jobs/notification_job.cr\nstruct NotificationJob\n  include JoobQ::Job\n\n  @queue   = \"notifications\"\n  @retries = 5\n  @expires = 6.hours.total_seconds.to_i\n\n  def initialize(@user_id : Int64, @type : String, @message : String, @data : Hash(String, String))\n  end\n\n  def perform\n    # Send real-time notification via WebSocket\n    NotificationChannel.notify_user(@user_id.to_s, {\n      \"type\" =\u003e @type,\n      \"message\" =\u003e @message,\n      \"data\" =\u003e @data,\n      \"timestamp\" =\u003e Time.utc.to_unix_ms\n    })\n\n    # Store notification in database for offline users\n    Notification.create!(\n      user_id: @user_id,\n      type: @type,\n      message: @message,\n      data: @data.to_json,\n      created_at: Time.utc\n    )\n\n    Log.info { \"Notification sent to user #{@user_id}\" }\n  end\nend\n\n# src/jobs/cleanup_job.cr\nstruct CleanupJob\n  include JoobQ::Job\n\n  @queue   = \"maintenance\"\n  @retries = 1\n  @expires = 2.hours.total_seconds.to_i\n\n  def initialize(@days_old : Int32 = 30)\n  end\n\n  def perform\n    cutoff_date = Time.utc - @days_old.days\n\n    # Clean up old temporary files\n    temp_files_deleted = cleanup_temp_files(cutoff_date)\n\n    # Clean up old sessions\n    sessions_deleted = cleanup_old_sessions(cutoff_date)\n\n    # Clean up old logs\n    logs_deleted = cleanup_old_logs(cutoff_date)\n\n    Log.info { \"Cleanup completed: #{temp_files_deleted} files, #{sessions_deleted} sessions, #{logs_deleted} logs\" }\n  end\n\n  private def cleanup_temp_files(cutoff_date : Time) : Int32\n    count = 0\n    Dir.glob(\"tmp/uploads/*\").each do |file_path|\n      if File.info(file_path).modification_time \u003c cutoff_date\n        File.delete(file_path)\n        count += 1\n      end\n    end\n    count\n  end\n\n  private def cleanup_old_sessions(cutoff_date : Time) : Int32\n    # This would depend on your session storage implementation\n    # Example for database-stored sessions:\n    # Session.where { created_at \u003c cutoff_date }.delete_all\n    0\n  end\n\n  private def cleanup_old_logs(cutoff_date : Time) : Int32\n    count = 0\n    Dir.glob(\"logs/*.log\").each do |log_file|\n      if File.info(log_file).modification_time \u003c cutoff_date\n        File.delete(log_file)\n        count += 1\n      end\n    end\n    count\n  end\nend\n\n# src/jobs/report_generation_job.cr\nstruct ReportGenerationJob\n  include JoobQ::Job\n\n  @queue   = \"reports\"\n  @retries = 2\n  @expires = 1.hour.total_seconds.to_i\n\n  def initialize(@user_id : Int64, @report_type : String, @date_range : String)\n  end\n\n  def perform\n    user = User.find(@user_id)\n    return unless user\n\n    # Generate report based on type\n    report_data = case @report_type\n    when \"user_activity\"\n      generate_user_activity_report(user, @date_range)\n    when \"sales\"\n      generate_sales_report(user, @date_range)\n    when \"analytics\"\n      generate_analytics_report(user, @date_range)\n    else\n      raise \"Unknown report type: #{@report_type}\"\n    end\n\n    # Save report to file\n    report_path = save_report(report_data, @report_type, @date_range)\n\n    # Notify user that report is ready\n    NotificationJob.new(\n      user_id: @user_id,\n      type: \"report_ready\",\n      message: \"Your #{@report_type} report is ready for download\",\n      data: {\"download_url\" =\u003e report_path}\n    ).enqueue\n\n    Log.info { \"Generated #{@report_type} report for user #{@user_id}\" }\n  end\n\n  private def generate_user_activity_report(user, date_range)\n    # Implementation for user activity report\n    {\"total_logins\" =\u003e 42, \"pages_visited\" =\u003e 156}\n  end\n\n  private def generate_sales_report(user, date_range)\n    # Implementation for sales report\n    {\"total_sales\" =\u003e 1250.50, \"orders_count\" =\u003e 28}\n  end\n\n  private def generate_analytics_report(user, date_range)\n    # Implementation for analytics report\n    {\"unique_visitors\" =\u003e 890, \"page_views\" =\u003e 3420}\n  end\n\n  private def save_report(data, type, date_range)\n    filename = \"#{type}_#{date_range}_#{Time.utc.to_unix}.json\"\n    filepath = Path[\"reports\", filename].to_s\n\n    Dir.mkdir_p(\"reports\")\n    File.write(filepath, data.to_json)\n\n    \"/downloads/#{filename}\"\n  end\nend\n```\n\n**Configure JoobQ in your application:**\n\n```crystal\n# src/my_app.cr (add to configuration)\nmodule MyApp\n  include Azu\n\n  configure do\n    # ... existing configuration ...\n\n    # JoobQ Configuration\n    JoobQ.configure do |config|\n      config.store = JoobQ::RedisStore.new(\n        host: ENV.fetch(\"REDIS_HOST\", \"localhost\"),\n        port: ENV.fetch(\"REDIS_PORT\", \"6379\").to_i,\n        password: ENV[\"REDIS_PASSWORD\"]?,\n        database: ENV.fetch(\"REDIS_DB\", \"0\").to_i\n      )\n\n      # Queue configurations\n      config.queues = {\n        \"default\" =\u003e 2,       # 2 workers for default queue\n        \"images\" =\u003e 1,        # 1 worker for image processing\n        \"notifications\" =\u003e 3, # 3 workers for notifications\n        \"reports\" =\u003e 1,       # 1 worker for report generation\n        \"maintenance\" =\u003e 1    # 1 worker for maintenance tasks\n      }\n\n      # Global job settings\n      config.failed_job_expiry = 7.days\n      config.dead_job_expiry = 30.days\n    end\n  end\nend\n\n# Load job files\nrequire \"./jobs/*\"\n```\n\n**Using Jobs in Your Application:**\n\n```crystal\n# In your endpoints\nstruct CreateUserEndpoint\n  include Azu::Endpoint(UserRequest, UserResponse)\n\n  post \"/api/users\"\n\n  def call : UserResponse\n    user = UserService.create_user(request)\n\n    # Queue welcome email job\n    EmailJob.new(\n      email_address: user.email,\n      subject: \"Welcome to MyApp!\",\n      body: \"Thank you for joining us, #{user.name}!\"\n    ).enqueue\n\n    # Queue image processing if profile image was uploaded\n    if request.profile_image\n      ImageProcessingJob.new(\n        user_id: user.id,\n        image_path: user.profile_image_url.not_nil!,\n        sizes: [\"thumbnail\", \"medium\", \"large\"]\n      ).enqueue\n    end\n\n    UserResponse.new(user)\n  end\nend\n\n# Schedule recurring jobs\n# This could be in a separate scheduler file or cron job\nCleanupJob.new(days_old: 30).enqueue_in(1.day)\n\n# Delayed job execution\nEmailJob.new(\n  email_address: \"user@example.com\",\n  subject: \"Reminder\",\n  body: \"Don't forget to complete your profile!\"\n).enqueue_in(2.hours)\n\n# High priority job\nNotificationJob.new(\n  user_id: user.id,\n  type: \"urgent\",\n  message: \"Security alert: New login detected\",\n  data: {\"ip\" =\u003e request.remote_address, \"timestamp\" =\u003e Time.utc.to_s}\n).enqueue_at(Time.utc)\n```\n\n**Worker Process:**\n\n```crystal\n# worker.cr\nrequire \"./src/my_app\"\n\n# Start the job processor\nJoobQ.start\n```\n\n**Running Jobs:**\n\n```bash\n# Start web server\ncrystal run server.cr\n\n# Start job workers (in separate terminal)\ncrystal run worker.cr\n\n# Or in production with multiple workers\ncrystal build worker.cr --release\n./worker \u0026\n./worker \u0026\n./worker \u0026\n```\n\n### 14. Complete Application Structure\n\nHere's how your complete project structure should look:\n\n```\nmy_app/\n├── src/\n│   ├── my_app.cr              # Main application configuration\n│   ├── models/\n│   │   ├── user.cr\n│   │   └── post.cr\n│   ├── services/\n│   │   ├── user_service.cr\n│   │   └── file_upload_service.cr\n│   ├── requests/\n│   │   ├── user_request.cr\n│   │   └── search_request.cr\n│   ├── responses/\n│   │   ├── user_response.cr\n│   │   └── user_list_response.cr\n│   ├── validators/\n│   │   ├── email_validator.cr\n│   │   └── file_validator.cr\n│   ├── endpoints/\n│   │   └── user_endpoints.cr\n│   ├── channels/\n│   │   ├── chat_channel.cr\n│   │   └── notification_channel.cr\n│   ├── middleware/\n│   │   ├── authentication_middleware.cr\n│   │   ├── cors_middleware.cr\n│   │   └── rate_limiting_middleware.cr\n│   ├── components/\n│   │   ├── user_counter_component.cr\n│   │   └── chat_component.cr\n│   ├── jobs/\n│   │   ├── email_job.cr\n│   │   ├── image_processing_job.cr\n│   │   ├── notification_job.cr\n│   │   ├── cleanup_job.cr\n│   │   └── report_generation_job.cr\n├── server.cr                  # Application entry point\n├── worker.cr                  # Background job worker\n└── public/                    # Static assets\n│   └── templates/\n│       ├── layouts/\n│       │   └── base.html\n│       └── users/\n│           ├── show.html\n│           └── index.html\n    └── uploads/\n        └── images/\n├── shard.yml                  # Dependencies\n```\n\nThis comprehensive quickstart guide demonstrates:\n\n- ✅ **Configuration**: Complete application setup with environment-aware settings\n- ✅ **Server Startup**: Dedicated entry point with conditional middleware loading\n- ✅ **Requests**: Type-safe request objects with comprehensive validation\n- ✅ **Custom Validators**: Reusable validation classes inheriting from Azu::Validator\n- ✅ **Models**: Database models with relationships, validations, and business logic\n- ✅ **Services**: Business logic separation and organization\n- ✅ **Responses**: Structured JSON and HTML responses\n- ✅ **Endpoints**: RESTful endpoints with proper error handling\n- ✅ **Templates**: Crinja templates with layouts and inheritance\n- ✅ **Components**: Live, interactive components with real-time updates\n- ✅ **Channels**: WebSocket channels for real-time communication\n- ✅ **Middleware**: Custom middleware for authentication, CORS, and rate limiting\n- ✅ **Background Jobs**: Asynchronous job processing with JoobQ for emails, image processing, and maintenance\n- ✅ **File Uploads**: Secure multipart file handling with validation\n- ✅ **Complete Structure**: Production-ready project organization\n\nEach component follows Azu's type-safe patterns and integrates seamlessly with the framework's architecture.\n\n## 🔌 Real-Time Features\n\n### WebSocket Channels\n\n```crystal\nclass ChatChannel \u003c Azu::Channel\n  ws \"/chat\"\n\n  def on_connect\n    broadcast(\"user_joined\", {user: current_user.name})\n  end\n\n  def on_message(message : String)\n    data = JSON.parse(message)\n    broadcast(\"message\", {\n      user: current_user.name,\n      text: data[\"text\"],\n      timestamp: Time.utc\n    })\n  end\n\n  def on_close(code, message)\n    broadcast(\"user_left\", {user: current_user.name})\n  end\nend\n```\n\n### Live Components\n\n```crystal\nclass CounterComponent\n  include Azu::Component\n\n  property count = 0\n\n  def content\n    div do\n      h2 { text \"Count: #{@count}\" }\n      button(onclick: \"increment\") { text \"+\" }\n      button(onclick: \"decrement\") { text \"-\" }\n    end\n  end\n\n  def on_event(name, data)\n    case name\n    when \"increment\"\n      @count += 1\n      refresh # Automatically updates the client\n    when \"decrement\"\n      @count -= 1\n      refresh\n    end\n  end\nend\n\n# In your template\n\u003c%= CounterComponent.mount.render %\u003e\n\u003c%= Azu::Spark.javascript_tag %\u003e\n```\n\n## 🛡️ Error Handling\n\nAzu provides comprehensive error handling with structured error responses:\n\n```crystal\n# Built-in error types\nraise Azu::Response::ValidationError.new(\"email\", \"is invalid\")\nraise Azu::Response::AuthenticationError.new(\"Login required\")\nraise Azu::Response::AuthorizationError.new(\"Admin access required\")\nraise Azu::Response::RateLimitError.new(retry_after: 60)\n\n# Custom error handling\nstruct MyEndpoint\n  include Azu::Endpoint(MyRequest, MyResponse)\n\n  def call : MyResponse\n    validate_request!\n    process_request\n  rescue ex : ValidationError\n    Azu::Response::ValidationError.new(ex.field_errors)\n  end\nend\n```\n\n### Error Response Formats\n\nAzu automatically formats errors based on content negotiation:\n\n```json\n// JSON format\n{\n  \"Title\": \"Validation Error\",\n  \"Detail\": \"Request validation failed\",\n  \"ErrorId\": \"err_123456\",\n  \"Fingerprint\": \"abc123\",\n  \"FieldErrors\": {\n    \"email\": [\"is invalid\", \"is required\"]\n  }\n}\n```\n\n## 📁 File Uploads\n\nHandle file uploads with built-in optimization and validation:\n\n```crystal\nstruct FileUploadRequest\n  include Azu::Request\n\n  getter avatar : Azu::Params::Multipart::File?\n  getter description : String\nend\n\nstruct UploadEndpoint\n  include Azu::Endpoint(FileUploadRequest, UploadResponse)\n\n  post \"/upload\"\n\n  def call : UploadResponse\n    if file = request.avatar\n      # File is automatically streamed to temp directory\n      validate_file_size!(file, max: 5.megabytes)\n      validate_file_type!(file, allowed: %w(.jpg .png .gif))\n\n      # Process the file\n      final_path = save_file(file)\n      file.cleanup # Clean up temp file\n\n      UploadResponse.new(url: final_path)\n    else\n      raise Azu::Response::ValidationError.new(\"avatar\", \"is required\")\n    end\n  end\nend\n```\n\n## 🎨 Templates \u0026 Views\n\n### Template Integration\n\n```crystal\nstruct UserPage\n  include Azu::Response\n  include Azu::Templates::Renderable\n\n  def initialize(@user : User, @posts : Array(Post))\n  end\n\n  def render\n    view \"users/show.html\", {\n      user: @user,\n      posts: @posts,\n      title: \"User Profile\"\n    }\n  end\nend\n\n# Template: templates/users/show.html\n# \u003ch1\u003e{{ user.name }}\u003c/h1\u003e\n# \u003cdiv class=\"posts\"\u003e\n#   {% for post in posts %}\n#     \u003carticle\u003e{{ post.content }}\u003c/article\u003e\n#   {% endfor %}\n# \u003c/div\u003e\n```\n\n### Hot Reloading\n\nTemplates automatically reload in development:\n\n```crystal\n# In development\nAzu::CONFIG.template_hot_reload = true\n\n# In production\nAzu::CONFIG.template_hot_reload = false\n```\n\n## ⚙️ Configuration\n\nAzu provides environment-aware configuration:\n\n```crystal\n# config/application.cr\nAzu::CONFIG.configure do |config|\n  config.port = ENV.fetch(\"PORT\", \"4000\").to_i\n  config.host = ENV.fetch(\"HOST\", \"0.0.0.0\")\n\n  # SSL configuration\n  config.ssl_cert = ENV[\"SSL_CERT\"]?\n  config.ssl_key = ENV[\"SSL_KEY\"]?\n\n  # File upload limits\n  config.upload.max_file_size = 10.megabytes\n  config.upload.temp_dir = \"/tmp/uploads\"\n\n  # Template configuration\n  config.templates.path = [\"templates\", \"views\"]\n  config.templates.error_path = \"errors\"\nend\n```\n\n## 🔒 Middleware\n\n### Built-in Middleware\n\n```crystal\nAzu.start [\n  Azu::Handler::Rescuer.new,     # Error handling\n  Azu::Handler::Logger.new,      # Request logging\n  Azu::Handler::CORS.new,        # CORS headers\n  Azu::Handler::CSRF.new,        # CSRF protection\n  Azu::Handler::Throttle.new,    # Rate limiting\n  Azu::Handler::Static.new,      # Static files\n  # Your endpoints here\n]\n```\n\n### Custom Middleware\n\n```crystal\nclass AuthenticationHandler\n  include HTTP::Handler\n\n  def call(context)\n    unless authenticated?(context)\n      context.response.status = HTTP::Status::UNAUTHORIZED\n      context.response.print \"Authentication required\"\n      return\n    end\n\n    call_next(context)\n  end\n\n  private def authenticated?(context)\n    context.request.headers[\"Authorization\"]?.try(\u0026.starts_with?(\"Bearer \"))\n  end\nend\n```\n\n## 🚄 Routing Performance\n\nAzu's router includes several performance optimizations:\n\n### Path Caching\n\n```crystal\n# Frequently accessed paths are cached\nrouter = Azu::Router.new\nrouter.get(\"/api/users/:id\", UserEndpoint.new)\n\n# First request: builds and caches path\n# Subsequent requests: uses cached resolution\n```\n\n### Method Pre-computation\n\n```crystal\n# HTTP methods are pre-computed at initialization\n# Zero allocation for method matching during request processing\n```\n\n### LRU Cache Management\n\n```crystal\n# Automatic cache eviction for optimal memory usage\n# Default cache size: 1000 entries\n# Configurable cache size and TTL\n```\n\n## 🧪 Testing\n\nAzu provides excellent testing support:\n\n```crystal\nrequire \"spec\"\nrequire \"azu\"\n\ndescribe UserEndpoint do\n  it \"creates user successfully\" do\n    request = UserRequest.new(\n      name: \"John Doe\",\n      email: \"john@example.com\",\n      age: 30\n    )\n\n    endpoint = UserEndpoint.new\n    endpoint.request = request\n\n    response = endpoint.call\n    response.should be_a(UserResponse)\n  end\nend\n\ndescribe ChatChannel do\n  it \"handles messages correctly\" do\n    socket = MockWebSocket.new\n    channel = ChatChannel.new(socket)\n\n    channel.on_message({\"text\" =\u003e \"Hello\"}.to_json)\n\n    # Verify message was processed\n  end\nend\n```\n\n## 📈 Performance Benchmarks\n\n| Feature             | Performance | Memory        |\n| ------------------- | ----------- | ------------- |\n| Router (cached)     | ~0.1ms      | 0 allocations |\n| Endpoint processing | ~0.5ms      | Minimal       |\n| WebSocket message   | ~0.2ms      | Constant      |\n| Template rendering  | ~1-5ms      | Cached        |\n| File upload (1MB)   | ~10ms       | Streaming     |\n\n## 🌟 Advanced Features\n\n### Content Negotiation\n\n```crystal\n# Automatic content type detection and response formatting\n# Supports: JSON, HTML, XML, Plain Text\n# Based on Accept headers and content type\n```\n\n### Request Validation\n\n```crystal\nstruct ProductRequest\n  include Azu::Request\n\n  getter name : String\n  getter price : Float64\n  getter category : String\n\n  validate name, presence: true, length: {min: 3, max: 50}\n  validate price, presence: true, numericality: {greater_than: 0}\n  validate category, inclusion: {in: %w(electronics books clothing)}\nend\n```\n\n### Environment Management\n\n```crystal\n# Automatic environment detection\ncase Azu::CONFIG.env\nwhen .development?\n  # Development-specific configuration\nwhen .production?\n  # Production optimizations\nwhen .test?\n  # Testing configuration\nend\n```\n\n## 🤝 Contributing\n\n1. Fork it (\u003chttps://github.com/azutoolkit/azu/fork\u003e)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n## 📚 Documentation\n\nFor comprehensive documentation, visit: [Azu Toolkit Documentation](https://azutopia.gitbook.io/azu/)\n\n## 🎯 Use Cases\n\nAzu excels in:\n\n- **API Development** - Type-safe REST and GraphQL APIs\n- **Real-time Applications** - Chat, gaming, live dashboards\n- **Web Applications** - Full-stack apps with server-side rendering\n- **Microservices** - High-performance service architectures\n- **IoT Backends** - Efficient WebSocket communication\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## 🙏 Contributors\n\n- [Elias J. Perez](https://github.com/eliasjpr) - creator and maintainer\n\n---\n\n**Ready to build something amazing?** Start with Azu today and experience the power of type-safe, high-performance web development in Crystal.\n\n## Performance Monitoring (Optional)\n\nPerformance monitoring in Azu is **completely optional** and disabled by default. When disabled, it adds zero overhead to your application.\n\n### Enabling Performance Monitoring\n\nSet the environment variable or enable it at compile time:\n\n```bash\n# Enable performance monitoring\nexport PERFORMANCE_MONITORING=true\n\n# Optional: Enable detailed profiling (development only)\nexport PERFORMANCE_PROFILING=true\nexport PERFORMANCE_MEMORY_MONITORING=true\n```\n\nOr compile with the flag:\n\n```bash\ncrystal build --define=performance_monitoring src/app.cr\n```\n\n### Using Performance Monitoring\n\nWhen enabled, add the performance monitor to your handler chain:\n\n```crystal\nrequire \"azu\"\n\n# Only available when performance monitoring is enabled\n{% if env(\"PERFORMANCE_MONITORING\") == \"true\" || flag?(:performance_monitoring) %}\n  performance_monitor = Azu::Handler::PerformanceMonitor.new\n{% end %}\n\nMyApp.start [\n  {% if env(\"PERFORMANCE_MONITORING\") == \"true\" || flag?(:performance_monitoring) %}\n    performance_monitor,\n  {% end %}\n  Azu::Handler::Logger.new,\n  Azu::Handler::Static.new\n]\n```\n\n### Performance Features (When Enabled)\n\n- **Request Metrics**: Response times, memory usage, error rates\n- **Component Tracking**: Mount/unmount/refresh performance\n- **Cache Metrics**: Hit rates, operation timings\n- **Memory Monitoring**: Leak detection and memory profiling\n- **Development Dashboard**: Real-time performance visualization\n\n### Zero Overhead When Disabled\n\nWhen `PERFORMANCE_MONITORING=false` (default):\n\n- No performance monitoring code is compiled\n- No memory overhead for metrics collection\n- No timing overhead for operations\n- Components run without tracking\n- Cache operations run without metrics\n\nThis makes Azu suitable for both development (with monitoring) and production (without overhead).\n\n### Performance API (When Enabled)\n\n```crystal\n# Enable performance tracking for specific components\ncomponent.enable_performance_tracking\n\n# Access performance data\nif monitor = Azu::CONFIG.performance_monitor\n  stats = monitor.stats\n  puts \"Average response time: #{stats.avg_response_time}ms\"\n  puts \"Error rate: #{stats.error_rate}%\"\nend\n```\n\n## Configuration\n\n```crystal\nAzu.configure do |config|\n  config.port = 4000\n  config.host = \"0.0.0.0\"\n\n  # Performance monitoring (only when enabled)\n  config.performance_enabled = true  # Default: false\n  config.performance_profiling_enabled = true  # Default: false\n  config.performance_memory_monitoring = true  # Default: false\nend\n```\n\n## Documentation\n\nFor complete documentation, visit: [Azu Documentation](docs/)\n\n## Contributing\n\nPlease read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct and development process.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fazutoolkit%2Fazu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fazutoolkit%2Fazu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fazutoolkit%2Fazu/lists"}