If you're coming from Ruby, you know Bundler. You know Gemfile, you know bundle install, and you know the joy of dependency hell at 2 AM when versions conflict.
Crystal has Shards - similar concept, cleaner execution, fewer headaches. Think of Shards as Bundler's younger sibling who learned from all the mistakes and came out more efficient.
Shards are Crystal's packages - libraries you can add to your project. The tool that manages them is also called shards (yes, confusing, but we'll survive).
In Ruby:
- Packages: Gems
- Package manager: Bundler
- Manifest: Gemfile
In Crystal:
- Packages: Shards
- Package manager: shards
- Manifest: shard.yml
Every Crystal project with dependencies needs a shard.yml file:
name: myapp
version: 0.1.0
authors:
- Your Name <you@example.com>
crystal: ">= 1.0.0"
dependencies:
kemal:
github: kemalcr/kemal
version: ~> 1.1.0
development_dependencies:
webmock:
github: manastech/webmock.cr
version: ~> 0.14.0
license: MITThis is your Gemfile equivalent, but in YAML.
Create a shard.yml and run:
shards installThis:
- Reads
shard.yml - Resolves dependencies
- Downloads shards to
lib/ - Creates
shard.lock(version lock file)
The shard.lock file (like Gemfile.lock) ensures everyone gets the same versions.
Visit crystalshards.org - a searchable directory of Crystal shards.
Most shards are on GitHub with names ending in .cr:
kemal.cr- web frameworkpg.cr- PostgreSQL driverredis.cr- Redis client
Check out awesome-crystal - a curated list of quality shards.
Most common - pull from GitHub:
dependencies:
mylib:
github: username/mylib
version: ~> 1.0.0Version specifiers:
1.0.0- exact version~> 1.0.0- >= 1.0.0 and < 1.1.0>= 1.0.0- any version >= 1.0.0< 2.0.0- any version < 2.0.0
dependencies:
mylib:
git: https://gitlab.com/username/mylib.git
version: ~> 1.0.0Useful for development:
dependencies:
mylib:
path: ../mylibdependencies:
experimental_lib:
github: username/experimental_lib
branch: develop
stable_lib:
github: username/stable_lib
commit: abc123After running shards install, require the shard in your code:
# shard.yml has:
# dependencies:
# kemal:
# github: kemalcr/kemal
# In your code:
require "kemal"
get "/" do
"Hello World!"
end
Kemal.runThe require statement finds the shard in the lib/ directory.
Dependencies only needed for development or testing:
development_dependencies:
spec-kemal:
github: kemalcr/spec-kemal
ameba:
github: crystal-ameba/amebaThese won't be installed when someone uses your shard as a dependency.
crystal init lib myshard
cd myshardThis creates:
myshard/
├── shard.yml
├── src/
│ ├── myshard.cr
│ └── myshard/
│ └── version.cr
├── spec/
│ ├── spec_helper.cr
│ └── myshard_spec.cr
├── .gitignore
├── LICENSE
└── README.md
src/myshard.cr - Main entry point:
require "./myshard/version"
module Myshard
# Your public API here
endsrc/myshard/version.cr - Version constant:
module Myshard
VERSION = "0.1.0"
endspec/myshard_spec.cr - Tests:
require "./spec_helper"
describe Myshard do
it "works" do
true.should eq(true)
end
endAdd functionality:
# src/myshard.cr
require "./myshard/version"
module Myshard
def self.greet(name : String) : String
"Hello, #{name}!"
end
def self.calculate(a : Int32, b : Int32) : Int32
a + b
end
endWrite tests:
# spec/myshard_spec.cr
require "./spec_helper"
describe Myshard do
describe ".greet" do
it "greets by name" do
Myshard.greet("Crystal").should eq("Hello, Crystal!")
end
end
describe ".calculate" do
it "adds numbers" do
Myshard.calculate(2, 3).should eq(5)
end
end
endRun tests:
crystal spec- Make sure shard.yml is complete:
name: myshard
version: 0.1.0
authors:
- Your Name <you@example.com>
description: |
A short description of what your shard does
crystal: ">= 1.0.0"
license: MIT
repository: https://github.com/yourusername/myshard- Tag a release:
git tag v0.1.0
git push origin v0.1.0- Submit to CrystalShards (optional): Once your repo is public and tagged, it should appear automatically on crystalshards.org.
Follow Semantic Versioning:
- MAJOR: Breaking changes (1.0.0 → 2.0.0)
- MINOR: New features, backwards compatible (1.0.0 → 1.1.0)
- PATCH: Bug fixes (1.0.0 → 1.0.1)
Include good docs:
module Myshard
# Greets a person by name
#
# ```
# Myshard.greet("Alice") # => "Hello, Alice!"
# ```
def self.greet(name : String) : String
"Hello, #{name}!"
end
endGenerate docs:
crystal docsView at docs/index.html.
Write comprehensive specs:
crystal spec --verboseInclude:
- Installation instructions
- Basic usage examples
- Link to documentation
- Contribution guidelines
Kemal is a popular web framework shard. Let's use it:
shard.yml:
name: webapp
version: 0.1.0
dependencies:
kemal:
github: kemalcr/kemal
version: ~> 1.1.0Install:
shards installsrc/webapp.cr:
require "kemal"
get "/" do
"Welcome to my Crystal web app!"
end
get "/hello/:name" do |env|
name = env.params.url["name"]
"Hello, #{name}!"
end
post "/echo" do |env|
body = env.request.body.try(&.gets_to_end) || ""
"You said: #{body}"
end
Kemal.runRun:
crystal run src/webapp.crVisit http://localhost:3000 and you have a web server!
- kemal - Web framework (Sinatra-like)
- amber - Full-stack framework (Rails-like)
- lucky - Type-safe web framework
- pg - PostgreSQL
- mysql - MySQL
- sqlite3 - SQLite
- redis - Redis
- spec - Built-in testing (like RSpec)
- webmock - HTTP request mocking
- timecop - Time manipulation for tests
- json - JSON parsing (built-in)
- yaml - YAML parsing (built-in)
- http - HTTP client/server (built-in)
- log - Logging (built-in)
- option_parser - Command-line parsing (built-in)
- colorize - Terminal colors (built-in)
- commander - CLI framework
shards install
# Error: Conflicting dependencies...Check which shards require conflicting versions and update your shard.yml constraints.
Update to latest versions:
shards updateIf compilation fails with "can't find shard":
# Make sure dependencies are installed
shards install
# Check that you're requiring correctly
require "shard_name" # not "lib/shard_name"# Remove from shard.yml, then:
rm -rf lib/
shards install| Feature | Bundler | Shards |
|---|---|---|
| Manifest | Gemfile | shard.yml |
| Lock file | Gemfile.lock | shard.lock |
| Install | bundle install | shards install |
| Update | bundle update | shards update |
| Init | bundle init | crystal init lib |
| Local dev | gem build / path | path in shard.yml |
The concepts are nearly identical, so your Bundler knowledge transfers directly.
- Create a new shard that provides string utility functions
- Add Kemal as a dependency and build a simple API
- Write comprehensive specs for your shard
- Generate documentation for your shard
- Explore crystalshards.org and try 3 interesting shards
- Shards are Crystal's packages, like Ruby gems
shard.ymldefines dependencies (like Gemfile)shards installfetches dependenciesshard.locklocks versions for reproducibility- Creating shards is straightforward with
crystal init lib - The shard ecosystem is smaller than gems but growing
- Most concepts from Bundler transfer directly
You now know how to use and create shards. Next, we'll explore one of Crystal's unique capabilities: C bindings. You'll learn how to call C libraries directly from Crystal, giving you access to decades of C code and letting you optimize performance-critical sections by dropping down to C when needed.
Continue to Chapter 08 - C Bindings: When You Need to Get Down and Dirty →