azure
azure
A Crystal client for Microsoft Azure services.
Installation
-
Add the dependency to your
shard.yml:dependencies: azure: github: jgaskins/azure -
Run
shards install
Usage
Blob Storage
require "azure/blob_storage"
# Connection string from "Access keys" in the Azure portal. Also accepts
# SAS connection strings and "UseDevelopmentStorage=true" for Azurite.
blobs = Azure::BlobStorage::Client.from_connection_string(ENV["AZURE_STORAGE_CONNECTION_STRING"])
# Or with an account name and key directly
blobs = Azure::BlobStorage::Client.new(
account_name: "mystorageaccount",
account_key: ENV["AZURE_STORAGE_ACCOUNT_KEY"],
)
# Or as a service principal (Microsoft Entra ID app registration). It needs a
# data role like "Storage Blob Data Contributor" on the account or container.
blobs = Azure::BlobStorage::Client.new(
account_name: "mystorageaccount",
credential: Azure::BlobStorage::ClientSecretCredential.new(
tenant_id: ENV["AZURE_TENANT_ID"],
client_id: ENV["AZURE_CLIENT_ID"],
client_secret: ENV["AZURE_CLIENT_SECRET"],
),
)
Connections
Each client keeps a pool of HTTP connections, so it's safe to share one client across fibers. By default, it opens as many connections as there are concurrent requests and keeps up to 25 of them open for reuse. If you make many requests at once (for example, fanning out uploads with a WaitGroup), you can limit how many connections it opens with max_connections, which makes further requests wait for a connection to free up. max_persistent_connections sets how many idle connections it keeps open.
blobs = Azure::BlobStorage::Client.from_connection_string(ENV["AZURE_STORAGE_CONNECTION_STRING"],
max_connections: 50,
max_persistent_connections: 10,
)
Uploading
upload accepts a String, Bytes, or any IO. Files up to 256MiB are sent in a single request; larger files and IOs of unknown size (like pipes) are streamed in 8MiB blocks, so they're never fully loaded into memory. Both thresholds are configurable with max_single_upload_size and block_size when creating the client.
File.open "photo.jpg" do |file|
blobs.upload "photos", "2026/10/photo.jpg", file,
content_type: "image/jpeg",
cache_control: "public, max-age=86400",
metadata: {"uploaded_by" => "jamie"}
end
# Raise Azure::BlobStorage::RequestError (code "BlobAlreadyExists") instead of replacing
blobs.upload "notes", "todo.txt", "buy milk", overwrite: false
Properties and metadata
if properties = blobs.properties("photos", "2026/10/photo.jpg")
properties.content_length # => 482113
properties.content_type # => "image/jpeg"
properties.last_modified # => 2026-10-09 18:59:36 UTC
properties.etag # => "\"0x8DD...\""
properties.metadata # => {"uploaded_by" => "jamie"}
end
blobs.metadata("photos", "2026/10/photo.jpg") # => {"uploaded_by" => "jamie"}
Both return nil if the blob doesn't exist.
Downloading
# Into memory
blobs.get("notes", "todo.txt") # => "buy milk"
# Streaming, for large or binary blobs
blobs.get "photos", "2026/10/photo.jpg" do |io, properties|
File.open("photo.jpg", "w") { |file| IO.copy io, file }
end
# Part of a blob
blobs.get "logs", "app.log", range: 0...1024
get returns nil (and doesn't call the block) if the blob doesn't exist. Other errors raise Azure::BlobStorage::RequestError, which exposes the HTTP status, the Azure error code, and the request_id.
There are also delete, create_container, and delete_container.
Development
The Blob Storage specs run against Azurite, and are marked pending if it isn't running:
docker run --rm -p 10000:10000 mcr.microsoft.com/azure-storage/azurite azurite-blob --blobHost 0.0.0.0
crystal spec spec/blob_storage
Set AZURE_STORAGE_CONNECTION_STRING to run them against a real storage account instead.
Contributing
- Fork it (https://github.com/jgaskins/azure/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Contributors
- Jamie Gaskins - creator and maintainer
azure
- 0
- 0
- 0
- 0
- 1
- about 4 hours ago
- October 10, 2026
MIT License
Fri, 06 Nov 2026 00:07:16 GMT