Claude Code for Crystal: Ruby-Like Syntax with C-Level Performance — Claude Skills 360 Blog
Blog / Systems / Claude Code for Crystal: Ruby-Like Syntax with C-Level Performance
Systems

Claude Code for Crystal: Ruby-Like Syntax with C-Level Performance

Published: January 7, 2027
Read time: 9 min read
By: Claude Skills 360

Crystal compiles to native machine code but reads like Ruby. Static typing with full type inference catches errors at compile time with no type annotations needed in most cases. Green threads (fibers) communicate through typed channels — Go-style CSP concurrency without callbacks. lib/fun declarations bind directly to C libraries. HTTP::Server handles thousands of concurrent requests with minimal memory. The Spec testing framework matches RSpec syntax. Shards manages dependencies through a simple YAML manifest. Claude Code generates Crystal types, fiber-based concurrent programs, C bindings, HTTP handlers, and test suites for high-performance Crystal services.

CLAUDE.md for Crystal Projects

## Crystal Stack
- Version: Crystal >= 1.13 (stable)
- Concurrency: fibers + channels (CSP) — prefer over threads for I/O-bound work
- HTTP: HTTP::Server with handlers, JSON::Serializable for (de)serialization
- DB: crystal-pg or crystal-sqlite3 with DB::Database + DB::ResultSet
- Testing: Spec — describe/it/expect blocks in spec/ directory
- Build: crystal build --release for production (enables LLVM optimizations)
- Shards: shards.yml for dependencies, `shards install` to resolve

Types and Structs

# src/models/order.cr — Crystal types with type inference
require "json"
require "time"

enum OrderStatus
  Pending
  Processing
  Shipped
  Delivered
  Cancelled
end

# Struct: value type, stack-allocated, copied on assignment
struct Money
  getter cents : Int64
  getter currency : String

  def initialize(@cents : Int64, @currency : String = "USD")
  end

  def +(other : Money) : Money
    raise ArgumentError.new("Currency mismatch") unless currency == other.currency
    Money.new(cents + other.cents, currency)
  end

  def to_s : String
    "$#{(cents / 100.0).round(2)}"
  end

  def_equals_and_hash cents, currency
end

# Class: reference type, heap-allocated
class Order
  include JSON::Serializable

  property id : String
  property customer_id : String
  property status : OrderStatus
  property total : Money
  property items : Array(OrderItem)
  property created_at : Time
  property shipped_at : Time?  # Nilable type — ? suffix
  property tracking_number : String?

  def initialize(
    @id : String,
    @customer_id : String,
    @items : Array(OrderItem),
    @status : OrderStatus = OrderStatus::Pending,
    @created_at : Time = Time.utc
  )
    @total = items.reduce(Money.new(0)) { |sum, item| sum + item.subtotal }
  end

  def pending? : Bool
    status.pending?
  end

  def cancelled? : Bool
    status.cancelled?
  end

  def high_value? : Bool
    total.cents > 10_000  # Underscores in numeric literals for readability
  end
end

struct OrderItem
  include JSON::Serializable

  getter product_id : String
  getter product_name : String
  getter quantity : Int32
  getter unit_price : Money

  def initialize(@product_id, @product_name, @quantity, @unit_price)
  end

  def subtotal : Money
    Money.new(unit_price.cents * quantity)
  end
end

Fibers and Channels

# src/worker/order_processor.cr — concurrent processing with fibers
require "channel"

struct JobResult
  getter order_id : String
  getter success : Bool
  getter error : String?

  def initialize(@order_id, @success, @error = nil)
  end
end

# Process orders concurrently with a worker pool
def process_orders_concurrent(orders : Array(String)) : Array(JobResult)
  results_channel = Channel(JobResult).new(orders.size)
  semaphore = Channel(Nil).new(10)  # Limit to 10 concurrent workers

  orders.each do |order_id|
    spawn do
      semaphore.send(nil)  # Acquire slot

      result = begin
        process_single_order(order_id)
        JobResult.new(order_id, success: true)
      rescue ex
        JobResult.new(order_id, success: false, error: ex.message)
      end

      results_channel.send(result)
      semaphore.receive  # Release slot
    end
  end

  # Collect all results
  Array(JobResult).new(orders.size) { results_channel.receive }
end

# Pipeline pattern: producer → channel → consumer
def setup_order_pipeline
  raw_orders = Channel(String).new(100)    # Buffered channel
  processed = Channel(JobResult).new(100)

  # Producer fiber: reads from queue
  spawn do
    loop do
      order_id = fetch_next_order_from_queue
      break if order_id.nil?
      raw_orders.send(order_id)
    end
    raw_orders.close
  end

  # Worker fibers (pool of 5)
  5.times do
    spawn do
      while (order_id = raw_orders.receive?)
        result = process_single_order(order_id) rescue nil
        processed.send(JobResult.new(order_id, success: !result.nil?))
      end
    end
  end

  # Consumer: aggregate results
  spawn do
    count = 0
    while (result = processed.receive?)
      count += 1
      puts "Processed #{count}: #{result.order_id} (#{result.success ? "OK" : result.error})"
    end
  end

  # Wait for all fibers
  Fiber.yield
end

HTTP Server

# src/server.cr — HTTP server with routing
require "http/server"
require "json"

class OrdersHandler
  include HTTP::Handler

  def call(context : HTTP::Server::Context)
    request = context.request
    response = context.response

    response.content_type = "application/json"

    case {request.method, request.path}
    when {"GET", /^\/orders\/(?<id>[a-z0-9-]+)$/}
      order_id = $~["id"]
      handle_get_order(order_id, response)

    when {"POST", "/orders"}
      handle_create_order(request, response)

    when {"GET", "/orders"}
      handle_list_orders(request, response)

    else
      call_next(context)
    end
  end

  private def handle_get_order(id : String, response : HTTP::Server::Response)
    order = Order.find(id)
    if order
      response.print order.to_json
    else
      response.status = HTTP::Status::NOT_FOUND
      response.print({error: "Order not found"}.to_json)
    end
  end

  private def handle_create_order(request : HTTP::Request, response : HTTP::Server::Response)
    body = request.body.try(&.gets_to_end) || ""

    begin
      params = JSON.parse(body)
      order = Order.create(
        customer_id: params["customer_id"].as_s,
        items: parse_items(params["items"]),
      )
      response.status = HTTP::Status::CREATED
      response.print order.to_json
    rescue ex : JSON::ParseException
      response.status = HTTP::Status::BAD_REQUEST
      response.print({error: "Invalid JSON: #{ex.message}"}.to_json)
    rescue ex : ArgumentError
      response.status = HTTP::Status::UNPROCESSABLE_ENTITY
      response.print({error: ex.message}.to_json)
    end
  end

  private def handle_list_orders(request : HTTP::Request, response : HTTP::Server::Response)
    status_param = request.query_params["status"]?
    status = status_param ? OrderStatus.parse(status_param) : nil

    orders = Order.list(status: status, limit: 50)
    response.print orders.to_json
  end

  private def parse_items(json : JSON::Any) : Array(OrderItem)
    json.as_a.map do |item|
      OrderItem.new(
        product_id: item["product_id"].as_s,
        product_name: item["product_name"].as_s,
        quantity: item["quantity"].as_i,
        unit_price: Money.new(item["unit_price_cents"].as_i64),
      )
    end
  end
end


# Main server entry point
server = HTTP::Server.new([
  HTTP::LogHandler.new,
  HTTP::ErrorHandler.new,
  OrdersHandler.new,
])

Signal::INT.trap { server.close }

address = server.bind_tcp("0.0.0.0", 8080)
puts "Listening on http://#{address}"
server.listen

C Bindings

# src/bindings/openssl.cr — C library bindings
@[Link("ssl")]
@[Link("crypto")]
lib LibSSL
  fun sha256 = SHA256(
    data : UInt8*,
    len : LibC::SizeT,
    md : UInt8*
  ) : UInt8*
end

def sha256(data : Bytes) : Bytes
  digest = Bytes.new(32)
  LibSSL.sha256(data, data.size, digest)
  digest
end

def sha256_hex(data : String) : String
  sha256(data.to_slice).hexstring
end

Spec Tests

# spec/order_spec.cr — RSpec-like testing
require "spec"
require "../src/models/order"

describe Order do
  describe "#high_value?" do
    it "returns false for orders under $100" do
      items = [OrderItem.new("p1", "Widget", 1, Money.new(5_00))]
      order = Order.new("ord-1", "cust-1", items)
      order.high_value?.should be_false
    end

    it "returns true for orders over $100" do
      items = [OrderItem.new("p1", "Widget", 2, Money.new(75_00))]
      order = Order.new("ord-1", "cust-1", items)
      order.high_value?.should be_true
    end
  end

  describe "#total" do
    it "sums all item subtotals" do
      items = [
        OrderItem.new("p1", "Item A", 2, Money.new(10_00)),
        OrderItem.new("p2", "Item B", 1, Money.new(25_00)),
      ]
      order = Order.new("ord-1", "cust-1", items)
      order.total.cents.should eq(45_00)
    end
  end

  describe "Money" do
    it "adds same-currency amounts" do
      a = Money.new(10_00)
      b = Money.new(25_50)
      (a + b).cents.should eq(35_50)
    end

    it "raises on currency mismatch" do
      expect_raises(ArgumentError, "Currency mismatch") do
        Money.new(100, "USD") + Money.new(100, "EUR")
      end
    end
  end
end

shards.yml

name: order-service
version: 0.1.0

dependencies:
  pg:
    github: will/crystal-pg
    version: ~> 0.28

  redis:
    github: stefanwille/crystal-redis
    version: ~> 2.9

targets:
  server:
    main: src/server.cr

For the Zig systems language that also compiles to native code but emphasizes explicit memory management over Crystal’s GC, see the Zig guide for comptime generics and C interop. For the Ruby web framework that Crystal’s syntax derives from, where you need ecosystem breadth over raw performance, the Rails guide covers the full Rails stack. The Claude Skills 360 bundle includes Crystal skill sets covering fiber concurrency, C bindings, and HTTP service development. Start with the free tier to try Crystal program generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free