Endpoints

Endpoints are the fundamental building blocks of Azu applications. They represent individual HTTP routes and encapsulate all the logic needed to handle specific requests. Each endpoint follows a strict contract pattern that ensures type safety, predictable behavior, and maintainable code.

Philosophy and Design Rationale

Azu's endpoint design is rooted in several core principles that distinguish it from traditional web frameworks:

1. Type Safety First

Unlike dynamic frameworks that rely on runtime validation, Azu enforces contracts at compile time. This means:

  • Compile-time guarantees: Type errors are caught before deployment

  • Self-documenting APIs: Contracts serve as living documentation

  • Refactoring safety: Changes are validated across the entire codebase

  • IDE support: Full autocomplete and error detection

2. Single Responsibility Principle

Each endpoint handles exactly one concern, avoiding the pitfalls of monolithic controllers:

# ✅ Good: Single responsibility
struct CreateUserEndpoint
  include Azu::Endpoint(CreateUserRequest, UserResponse)
  post "/users"

  def call : UserResponse
    # Only user creation logic
    user = User.create!(create_user_request.to_h)
    UserResponse.new(user)
  end
end

# ❌ Bad: Multiple responsibilities
struct UserController
  # Handles create, read, update, delete, search, etc.
  # Becomes hard to test and maintain
end

3. Explicit Contracts Over Implicit Behavior

Azu makes all data flow explicit through contracts:

# Request contract defines exactly what the endpoint expects
struct CreateUserRequest
  include Azu::Request

  getter name : String
  getter email : String
  getter age : Int32?

  # Validation rules are explicit
  validate name, presence: true, length: {min: 2, max: 100}
  validate email, presence: true, format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validate age, numericality: {greater_than: 0, less_than: 150}, allow_nil: true
end

# Response contract defines exactly what the endpoint returns
struct UserResponse
  include Azu::Response

  def initialize(@user : User)
  end

  def render
    {
      id: @user.id,
      name: @user.name,
      email: @user.email,
      created_at: @user.created_at.to_rfc3339
    }.to_json
  end
end

4. Testability by Design

Endpoints are designed to be easily testable in isolation:

# Each endpoint can be tested independently
describe CreateUserEndpoint do
  it "creates a user successfully" do
    endpoint = CreateUserEndpoint.new
    request = HTTP::Request.new("POST", "/users",
      headers: HTTP::Headers{"Content-Type" => "application/json"},
      body: {name: "John", email: "john@example.com"}.to_json
    )
    context = HTTP::Server::Context.new(request, HTTP::IO.new(IO::Memory.new))

    response = endpoint.call(context)

    response.status_code.should eq(201)
    JSON.parse(response.body)["name"].should eq("John")
  end
end

Detailed Endpoint Anatomy

Understanding the internal structure of endpoints is crucial for building robust applications.

Core Components

Endpoint Structure Breakdown

struct UserEndpoint
  # 1. Module inclusion - defines the contract
  include Azu::Endpoint(UserRequest, UserResponse)

  # 2. Route definition - maps HTTP method to path
  get "/users/:id"

  # 3. Core handler - implements the business logic
  def call : UserResponse
    # 4. Parameter extraction
    user_id = params["id"].to_i64

    # 5. Request validation
    request = user_request
    unless request.valid?
      raise Azu::Response::ValidationError.new(request.errors)
    end

    # 6. Business logic
    user = User.find(user_id)
    raise Azu::Response::NotFound.new unless user

    # 7. Response creation
    UserResponse.new(user)
  end
end

Instance Variables and State

Each endpoint instance maintains specific state during request processing:

module Azu
  module Endpoint(Request, Response)
    # Request context - HTTP server context
    @context : HTTP::Server::Context? = nil

    # Parsed parameters - route, query, and body parameters
    @params : Params(Request)? = nil

    # Request object - lazy-loaded contract instance
    @request_object : Request? = nil
  end
end

Lifecycle Methods

Endpoints provide several lifecycle methods for different stages:

struct LifecycleEndpoint
  include Azu::Endpoint(LifecycleRequest, LifecycleResponse)

  get "/lifecycle/:id"

  def call : LifecycleResponse
    # Pre-processing
    before_call

    # Main execution
    result = execute_business_logic

    # Post-processing
    after_call(result)

    LifecycleResponse.new(result)
  end

  private def before_call
    # Setup, logging, metrics
    Log.info("Processing request", {
      user_id: params["id"],
      timestamp: Time.utc
    })
  end

  private def execute_business_logic
    # Core business logic
    user = User.find(params["id"].to_i64)
    user.process_request(lifecycle_request)
  end

  private def after_call(result)
    # Cleanup, notifications
    Metrics.increment("requests.processed")
    notify_analytics(result)
  end
end

Type Safety and Contract Enforcement

Azu's type system provides compile-time guarantees that eliminate entire classes of runtime errors.

Compile-Time Contract Validation

Request Contract Enforcement

Request contracts ensure that endpoints only receive valid, typed data:

# Define the contract
struct CreateProductRequest
  include Azu::Request

  getter name : String
  getter price : Float64
  getter category : String
  getter description : String?
  getter tags : Array(String)

  # Validation rules
  validate name, presence: true, length: {min: 3, max: 100}
  validate price, presence: true, numericality: {greater_than: 0}
  validate category, presence: true, inclusion: {in: %w(electronics books clothing)}
  validate description, length: {max: 500}, allow_nil: true
  validate tags, length: {max: 10}
end

# Use in endpoint - compile-time safety
struct CreateProductEndpoint
  include Azu::Endpoint(CreateProductRequest, ProductResponse)

  post "/products"

  def call : ProductResponse
    # Type-safe access to request data
    request = create_product_request

    # Compile-time guarantee: all fields are properly typed
    product = Product.create!(
      name: request.name,           # String
      price: request.price,         # Float64
      category: request.category,   # String
      description: request.description, # String?
      tags: request.tags            # Array(String)
    )

    ProductResponse.new(product)
  end
end

Response Contract Enforcement

Response contracts ensure consistent, structured output:

# Define response structure
struct ProductResponse
  include Azu::Response

  def initialize(@product : Product, @include_details : Bool = false)
  end

  def render
    case context.request.headers["Accept"]?
    when .try(&.includes?("application/json"))
      render_json
    when .try(&.includes?("text/html"))
      render_html
    when .try(&.includes?("application/xml"))
      render_xml
    else
      render_json # Default
    end
  end

  private def render_json
    {
      id: @product.id,
      name: @product.name,
      price: @product.price,
      category: @product.category,
      in_stock: @product.quantity > 0,
      created_at: @product.created_at.to_rfc3339
    }.to_json
  end

  private def render_html
    view "products/show.html", {
      product: @product,
      include_details: @include_details
    }
  end

  private def render_xml
    <<-XML
    <?xml version="1.0" encoding="UTF-8"?>
    <product id="#{@product.id}">
      <name>#{@product.name}</name>
      <price>#{@product.price}</price>
      <category>#{@product.category}</category>
      <in_stock>#{@product.quantity > 0}</in_stock>
    </product>
    XML
  end
end

Method Signature Enforcement

The call method must return the exact response type:

struct StrictEndpoint
  include Azu::Endpoint(StrictRequest, StrictResponse)

  get "/strict/:id"

  def call : StrictResponse
    # ✅ This compiles - correct return type
    return StrictResponse.new(data)

    # ❌ This won't compile - wrong return type
    # return "string"

    # ❌ This won't compile - undefined method
    # return strict_request.invalid_field
  end
end

Generic Type Constraints

Endpoints can work with generic types while maintaining type safety:

# Generic response for collections
struct CollectionResponse(T)
  include Azu::Response

  def initialize(@items : Array(T), @total_count : Int32, @page : Int32)
  end

  def render
    {
      items: @items.map(&.to_json),
      pagination: {
        page: @page,
        total_count: @total_count,
        has_next: (@page * 20) < @total_count
      }
    }.to_json
  end
end

# Type-safe collection endpoint
struct UsersIndexEndpoint
  include Azu::Endpoint(UsersIndexRequest, CollectionResponse(User))

  get "/users"

  def call : CollectionResponse(User)
    request = users_index_request
    users = User.all.limit(request.limit).offset(request.offset)
    total = User.count

    CollectionResponse(User).new(users, total, request.page)
  end
end

Compile-Time Error Examples

Azu catches common mistakes at compile time:

# ❌ Compilation Error: Wrong response type
struct WrongResponseEndpoint
  include Azu::Endpoint(UserRequest, UserResponse)

  def call : String  # Should return UserResponse
    "Hello World"
  end
end

# ❌ Compilation Error: Missing required field
struct IncompleteRequest
  include Azu::Request
  getter name : String
  # Missing email field that endpoint expects
end

# ❌ Compilation Error: Undefined method
struct UndefinedMethodEndpoint
  include Azu::Endpoint(UserRequest, UserResponse)

  def call : UserResponse
    user_request.non_existent_field  # Method doesn't exist
    UserResponse.new(user)
  end
end

This type safety ensures that:

  • API contracts are enforced: Clients and servers agree on data structure

  • Refactoring is safe: Changes are validated across the entire codebase

  • Runtime errors are minimized: Type mismatches are caught early

  • Documentation is accurate: Code serves as living documentation

Route Registration and Advanced Routing

Azu's routing system is built on the high-performance Radix tree with intelligent caching and advanced features for complex routing scenarios.

Route Registration Fundamentals

Basic Route Registration

Routes are defined directly on endpoints using HTTP method macros:

struct UserEndpoint
  include Azu::Endpoint(UserRequest, UserResponse)

  # Standard HTTP methods
  get "/users"                    # GET /users
  post "/users"                   # POST /users
  get "/users/:id"                # GET /users/123
  put "/users/:id"                # PUT /users/123
  patch "/users/:id"              # PATCH /users/123
  delete "/users/:id"             # DELETE /users/123
  head "/users/:id"               # HEAD /users/123
  options "/users"                # OPTIONS /users

  def call : UserResponse
    # Route handling logic
    handle_user_request
  end
end

Dynamic Route Parameters

Dynamic segments capture values from the URL path:

struct ComplexEndpoint
  include Azu::Endpoint(ComplexRequest, ComplexResponse)

  # Single parameter
  get "/users/:id"

  # Multiple parameters
  get "/users/:user_id/posts/:post_id"

  # Nested resources
  get "/organizations/:org_id/teams/:team_id/members/:member_id"

  # Optional parameters (using query strings)
  get "/search"  # ?q=term&page=1&limit=10

  def call : ComplexResponse
    case context.request.path
    when .starts_with?("/users/") && params["user_id"]? && params["post_id"]?
      # Handle /users/:user_id/posts/:post_id
      user_id = params["user_id"].to_i64
      post_id = params["post_id"].to_i64
      handle_user_post(user_id, post_id)

    when .starts_with?("/organizations/")
      # Handle nested organization routes
      org_id = params["org_id"].to_i64
      team_id = params["team_id"].to_i64
      member_id = params["member_id"].to_i64
      handle_organization_member(org_id, team_id, member_id)

    when .starts_with?("/search")
      # Handle search with query parameters
      handle_search

    else
      # Handle simple user routes
      user_id = params["id"].to_i64
      handle_user(user_id)
    end
  end

  private def handle_user_post(user_id : Int64, post_id : Int64) : ComplexResponse
    user = User.find(user_id)
    post = user.posts.find(post_id)
    ComplexResponse.new(user: user, post: post)
  end

  private def handle_organization_member(org_id : Int64, team_id : Int64, member_id : Int64) : ComplexResponse
    org = Organization.find(org_id)
    team = org.teams.find(team_id)
    member = team.members.find(member_id)
    ComplexResponse.new(organization: org, team: team, member: member)
  end

  private def handle_search : ComplexResponse
    request = complex_request
    results = SearchService.search(
      query: request.query,
      page: request.page || 1,
      limit: request.limit || 20
    )
    ComplexResponse.new(search_results: results)
  end
end

Route Constraints

Add constraints to route parameters for validation and security:

struct ConstrainedEndpoint
  include Azu::Endpoint(ConstrainedRequest, ConstrainedResponse)

  # Numeric ID constraint
  get "/users/:id" do |route|
    route.constraints(id: /\d+/)
  end

  # UUID constraint
  get "/documents/:uuid" do |route|
    route.constraints(uuid: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i)
  end

  # File extension constraint
  get "/files/:name.:ext" do |route|
    route.constraints(ext: /jpg|png|gif|pdf/)
  end

  # Custom constraint with validation
  get "/products/:slug" do |route|
    route.constraints(slug: /[a-z0-9-]+/)  # Only lowercase, numbers, hyphens
  end

  # Multiple constraints
  get "/api/v:version/:resource/:id" do |route|
    route.constraints(
      version: /\d+\.\d+/,           # Version format: 1.0, 2.1, etc.
      resource: /users|posts|comments/,  # Allowed resources
      id: /\d+/                      # Numeric ID
    )
  end

  def call : ConstrainedResponse
    # Constraints ensure parameters are valid before reaching business logic
    case context.request.path
    when .starts_with?("/users/")
      user_id = params["id"].to_i64  # Guaranteed to be numeric
      handle_user(user_id)

    when .starts_with?("/documents/")
      uuid = params["uuid"]  # Guaranteed to be valid UUID
      handle_document(uuid)

    when .starts_with?("/files/")
      filename = params["name"]
      extension = params["ext"]  # Guaranteed to be valid extension
      handle_file(filename, extension)

    when .starts_with?("/products/")
      slug = params["slug"]  # Guaranteed to be valid slug
      handle_product(slug)

    when .starts_with?("/api/")
      version = params["version"]  # Guaranteed to be valid version
      resource = params["resource"]  # Guaranteed to be valid resource
      id = params["id"].to_i64  # Guaranteed to be numeric
      handle_api_request(version, resource, id)
    end
  end
end

Route Helpers and URL Generation

Azu automatically generates route helper methods for URL generation:

struct UserEndpoint
  include Azu::Endpoint(UserRequest, UserResponse)

  get "/users/:id"
  get "/users/:id/posts/:post_id"
  get "/users/:id/profile"

  def call : UserResponse
    # Route helpers are automatically generated
    user_path = UserEndpoint.path(id: 123)                    # "/users/123"
    user_post_path = UserEndpoint.path(id: 123, post_id: 456) # "/users/123/posts/456"
    user_profile_path = UserEndpoint.path(id: 123)            # "/users/123/profile"

    # Use in redirects
    redirect(UserEndpoint.path(id: user.id))

    # Use in templates
    UserResponse.new(user)
  end
end

# In templates, route helpers are available as filters
# {{ user_path(id: user.id) }} -> "/users/123"
# {{ user_post_path(id: user.id, post_id: post.id) }} -> "/users/123/posts/456"

Advanced Routing Patterns

Resource-Based Routing

struct ResourceEndpoint
  include Azu::Endpoint(ResourceRequest, ResourceResponse)

  # Standard RESTful routes
  get "/articles"                    # Index
  post "/articles"                   # Create
  get "/articles/:id"                # Show
  put "/articles/:id"                # Update
  patch "/articles/:id"              # Partial update
  delete "/articles/:id"             # Destroy

  # Custom actions
  post "/articles/:id/publish"       # Publish action
  post "/articles/:id/unpublish"     # Unpublish action
  post "/articles/:id/duplicate"     # Duplicate action

  # Nested resources
  get "/articles/:id/comments"       # Article comments
  post "/articles/:id/comments"      # Create comment
  delete "/articles/:id/comments/:comment_id" # Delete comment

  def call : ResourceResponse
    case context.request.path
    when .ends_with?("/publish")
      handle_publish_action
    when .ends_with?("/unpublish")
      handle_unpublish_action
    when .ends_with?("/duplicate")
      handle_duplicate_action
    when .includes?("/comments")
      handle_comments
    else
      handle_standard_resource_action
    end
  end

  private def handle_publish_action : ResourceResponse
    article = Article.find(params["id"].to_i64)
    article.publish!
    ResourceResponse.new(article, action: "published")
  end

  private def handle_unpublish_action : ResourceResponse
    article = Article.find(params["id"].to_i64)
    article.unpublish!
    ResourceResponse.new(article, action: "unpublished")
  end

  private def handle_duplicate_action : ResourceResponse
    original = Article.find(params["id"].to_i64)
    duplicated = original.duplicate
    ResourceResponse.new(duplicated, action: "duplicated")
  end

  private def handle_comments : ResourceResponse
    article = Article.find(params["id"].to_i64)

    if params["comment_id"]?
      # Delete specific comment
      comment = article.comments.find(params["comment_id"].to_i64)
      comment.destroy
      ResourceResponse.new(article, action: "comment_deleted")
    else
      # List or create comments
      case method
      when .get?
        comments = article.comments
        ResourceResponse.new(article, comments: comments)
      when .post?
        comment = article.comments.create!(resource_request.to_h)
        ResourceResponse.new(article, comment: comment)
      end
    end
  end
end

API Versioning

struct VersionedEndpoint
  include Azu::Endpoint(VersionedRequest, VersionedResponse)

  # Version-specific routes
  get "/api/v1/users/:id"
  get "/api/v2/users/:id"
  get "/api/v3/users/:id"

  def call : VersionedResponse
    version = extract_api_version
    user_id = params["id"].to_i64

    case version
    when "v1"
      handle_v1_request(user_id)
    when "v2"
      handle_v2_request(user_id)
    when "v3"
      handle_v3_request(user_id)
    else
      raise Azu::Response::BadRequest.new("Unsupported API version")
    end
  end

  private def extract_api_version : String
    # Extract version from path
    path = context.request.path
    if match = path.match(/\/api\/(v\d+)\//)
      match[1]
    else
      "v1" # Default version
    end
  end

  private def handle_v1_request(user_id : Int64) : VersionedResponse
    # Legacy API format
    user = User.find(user_id)
    VersionedResponse.new(
      user: user,
      format: "legacy",
      deprecated: true
    )
  end

  private def handle_v2_request(user_id : Int64) : VersionedResponse
    # Current API format
    user = User.find(user_id)
    VersionedResponse.new(
      user: user,
      format: "current",
      deprecated: false
    )
  end

  private def handle_v3_request(user_id : Int64) : VersionedResponse
    # Future API format with additional data
    user = User.find(user_id)
    VersionedResponse.new(
      user: user,
      format: "future",
      deprecated: false,
      metadata: {
        "schema_version" => "3.0",
        "features" => ["enhanced_profile", "social_links"]
      }
    )
  end
end

Performance Characteristics

Azu's router is optimized for high-performance applications:

# Route resolution performance metrics
module Azu
  class Router
    # LRU cache for frequently accessed routes
    @route_cache = LRUCache(String, Route).new(capacity: 1000)

    # Radix tree for O(log n) lookup
    @radix_tree = Radix::Tree(Route).new

    def find_route(method : String, path : String) : Route?
      cache_key = "#{method}:#{path}"

      # Check cache first (O(1))
      if cached_route = @route_cache[cache_key]?
        return cached_route
      end

      # Perform radix tree lookup (O(log n))
      route = @radix_tree.find(path, method)

      # Cache for future requests
      @route_cache[cache_key] = route if route

      route
    end
  end
end

# Performance benchmarks
# - Route resolution: ~0.1ms for cached routes
# - Cache hit ratio: >95% in typical applications
# - Memory usage: Constant with LRU eviction
# - Concurrent access: Lock-free for read operations

Full Request Lifecycle

The request lifecycle in Azu is a comprehensive journey from HTTP request to response, involving multiple layers of processing, validation, and error handling.

Complete Lifecycle Overview

Detailed Lifecycle Stages

1. HTTP Request Reception

# Raw HTTP request received by server
GET /users/123?include=posts HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
User-Agent: Mozilla/5.0 (compatible; MyApp/1.0)
X-Request-ID: req_123456789

2. Middleware Stack Processing

# Middleware processes request in order
ExampleApp.start [
  Azu::Handler::Rescuer.new,           # Error handling
  Azu::Handler::Logger.new,            # Request logging
  Azu::Handler::CORS.new,              # CORS headers
  Azu::Handler::Authentication.new,    # JWT validation
  Azu::Handler::RateLimit.new,         # Rate limiting
  Azu::Handler::Compression.new,       # Response compression
  Azu::Handler::Security.new,          # Security headers
  # ... more middleware
]

# Each middleware can modify the request context
class CustomMiddleware
  include HTTP::Handler

  def call(context)
    # Pre-processing
    start_time = Time.utc
    request_id = generate_request_id
    context.set("request_id", request_id)
    context.set("start_time", start_time)

    # Add request tracking
    Log.info("Request started", {
      request_id: request_id,
      method: context.request.method,
      path: context.request.path,
      ip: context.request.remote_address.try(&.address)
    })

    # Call next middleware/endpoint
    call_next(context)

    # Post-processing
    duration = Time.utc - start_time
    Log.info("Request completed", {
      request_id: request_id,
      duration: duration.total_milliseconds,
      status: context.response.status_code
    })
  end
end

3. Router Resolution

# Radix tree finds matching route
router = Azu::CONFIG.router

# Route resolution process
route = router.find_route("GET", "/users/123")
# Returns: UserShowEndpoint with params: {"id" => "123"}

# Route caching for performance
cache_key = "GET:/users/123"
if cached_route = route_cache[cache_key]?
  route = cached_route  # O(1) cache hit
else
  route = radix_tree.find("/users/123", "GET")  # O(log n) tree lookup
  route_cache[cache_key] = route  # Cache for future requests
end

4. Endpoint Instantiation

# Framework creates endpoint instance
endpoint = UserShowEndpoint.new

# Instance variables initialized
endpoint.instance_variable_set(:@context, context)
endpoint.instance_variable_set(:@params, Params(UserShowRequest).new(context.request))
endpoint.instance_variable_set(:@request_object, nil)  # Lazy-loaded

# Endpoint is now ready to handle the request

5. Request Parsing and Contract Creation

# Parameters extracted from multiple sources
params = {
  # Route parameters
  "id" => "123",

  # Query parameters
  "include" => "posts,comments",
  "page" => "1",
  "limit" => "20",

  # Headers (if applicable)
  "authorization" => "Bearer token123",

  # Body (for POST/PUT requests)
  "name" => "John Doe",
  "email" => "john@example.com"
}

# Request contract instantiated based on content type
request = case context.request.headers["Content-Type"]?
when .try(&.includes?("application/json"))
  UserShowRequest.from_json(context.request.body.try(&.gets_to_end) || "{}")
when .try(&.includes?("application/x-www-form-urlencoded"))
  UserShowRequest.from_query(params)
else
  UserShowRequest.from_query(params)  # Default to query params
end

6. Validation Execution

# Schema validation runs automatically
unless request.valid?
  # Collect all validation errors
  errors = request.errors.group_by(&.field).transform_values(&.map(&.message))

  # Raise validation error with proper HTTP status
  raise Azu::Response::ValidationError.new(errors)
end

# Custom validation (if defined)
begin
  request.validate!
rescue ex : ValidationError
  raise Azu::Response::ValidationError.new(ex.errors)
end

# Example validation errors
{
  "email" => ["is required", "must be a valid email address"],
  "password" => ["must be at least 8 characters"],
  "age" => ["must be a positive integer"]
}

7. Business Logic Execution

def call : UserShowResponse
  # Authorization check
  current_user = context.get("current_user")
  user_id = params["id"].to_i64

  unless current_user.can_view_user?(user_id)
    raise Azu::Response::AuthorizationError.new(
      "Cannot view user #{user_id}"
    )
  end

  # Database query with error handling
  user = begin
    User.find(user_id)
  rescue ex : RecordNotFound
    raise Azu::Response::NotFound.new("/users/#{user_id}")
  end

  # Business logic execution
  user.increment_view_count!

  # Background processing
  spawn do
    notify_analytics(user, current_user)
    update_user_statistics(user)
  end

  # Response creation with conditional data
  include_posts = request.include_posts?
  include_comments = request.include_comments?

  UserShowResponse.new(
    user: user,
    include_posts: include_posts,
    include_comments: include_comments
  )
end

8. Response Creation and Rendering

# Response object created
response = UserShowResponse.new(user, include_posts: true, include_comments: false)

# Content negotiation based on Accept header
def render
  accept_header = context.request.headers["Accept"]?

  case accept_header
  when .try(&.includes?("application/json"))
    render_json
  when .try(&.includes?("text/html"))
    render_html
  when .try(&.includes?("application/xml"))
    render_xml
  when .try(&.includes?("text/plain"))
    render_text
  else
    render_json  # Default to JSON
  end
end

# Response rendering with proper content type
private def render_json
  context.response.content_type = "application/json"
  {
    id: @user.id,
    name: @user.name,
    email: @user.email,
    posts: @include_posts ? @user.posts.map(&.to_json) : nil,
    comments: @include_comments ? @user.comments.map(&.to_json) : nil,
    created_at: @user.created_at.to_rfc3339,
    updated_at: @user.updated_at.to_rfc3339
  }.to_json
end

private def render_html
  context.response.content_type = "text/html"
  view "users/show.html", {
    user: @user,
    include_posts: @include_posts,
    include_comments: @include_comments
  }
end

Error Handling Throughout the Lifecycle

Performance Optimization Points

1. Route Caching

# LRU cache for frequently accessed routes
@route_cache = LRUCache(String, Route).new(capacity: 1000)

# Cache hit provides O(1) lookup
if cached_route = @route_cache[cache_key]?
  return cached_route
end

2. Parameter Parsing Optimization

# Efficient parameter access with caching
def call : UserResponse
  # Cache parameter conversion
  user_id = @user_id ||= params["id"].to_i64

  # Use efficient database queries
  user = User.find(user_id, select: "id, name, email")

  UserResponse.new(user)
end

3. Background Processing

# Non-blocking background tasks
def call : UserResponse
  user = User.find(params["id"].to_i64)

  # Spawn background processing
  spawn do
    user.increment_view_count!
    notify_analytics(user)
    update_search_index(user)
  end

  # Return immediately
  UserResponse.new(user)
end

4. Response Streaming

# Stream large responses
def call : StreamingResponse
  context.response.content_type = "text/event-stream"
  context.response.headers["Cache-Control"] = "no-cache"

  spawn do
    stream_large_dataset(context.response)
  end

  StreamingResponse.new
end

private def stream_large_dataset(response)
  User.all.each_slice(100) do |users|
    response << "data: #{users.to_json}\n\n"
    Fiber.yield  # Allow other fibers to run
  end
end

This comprehensive lifecycle ensures that every request is processed consistently, safely, and efficiently, with proper error handling and performance optimizations at each stage.

Last updated

Was this helpful?