Build web applications and APIs with minimal code. 3.8k+ ⭐, 5M+ downloads since 2015.
-
Create a new project
crystal init app my-app cd my-app -
Add Kemal to
shard.ymldependencies: kemal: github: kemalcr/kemal
-
Write your app - replace
src/my-app.crwith:require "kemal" get "/" do "Hello World!" end get "/api" do |env| env.response.content_type = "application/json" {status: "ok"}.to_json end Kemal.run
-
Install dependencies and run
shards install crystal run src/my-app.cr
Visit http://localhost:3000 - done in under a minute. 🚀
| Problem | Solution |
|---|---|
| "I want C-level performance with Ruby-like syntax" | Crystal + Kemal - fast by default |
| "I need WebSocket support out of the box" | Built-in, no extra gems |
| "Building a JSON API" | Native JSON handling, minimal boilerplate |
| "I want a framework that stays out of my way" | No forced ORM, no magic, just Crystal |
- 🚀 High-performance by default - Built on Crystal with a thin abstraction layer
- 🌐 Full REST & HTTP support - All HTTP verbs with a straightforward routing DSL
- 🔌 WebSocket & real-time - First-class WebSocket support
- 📦 JSON-first APIs - Native JSON handling
- 🗄️ Static assets - Serve files efficiently from the same application
- 📝 Template engine - Built-in ECR template engine
- 🔒 Composable middleware - Logging, auth, rate limiting, metrics
- 🎯 Ergonomic request/response - Simple params, headers, cookies via context
- 🍪 Session management - Production-ready via kemal-session
Kemal is built on Crystal. It compiles to native code and starts instantly.
| Test | Results |
|---|---|
| JSON serialization (100 conn) | ~50,000 req/sec |
| Hello World (100 conn) | ~85,000 req/sec |
| Static file serving | ~40,000 req/sec |
| Memory per request | ~0.5 KB |
| Binary size | ~2 MB (with dependencies) |
No JVM, no Node, no Ruby VM. Just a native binary.
| Feature | Kemal | Sinatra | Flask | Express |
|---|---|---|---|---|
| Performance (req/sec) | ~85K | ~5K | ~3K | ~15K |
| WebSocket built-in | ✅ | - | - | - |
| Single binary deploy | ✅ | - | - | - |
| JSON handling | Native | Gem | Extension | Native |
| Type safety | ✅ | - | - | - |
| Concurrency | Fibers | Threads | Threads | Async |
Kemal aims to be a simple, fast and reliable foundation for building production-grade web applications and APIs in Crystal.
- Simple core, powerful building blocks - The core is intentionally simple. Most power comes from Crystal and middleware, not hidden magic.
- Performance as a baseline - Crystal's native speed means high performance is the default. Kemal keeps abstractions thin.
- Minimal assumptions, maximum flexibility - No forced ORM, template engine, or project layout. Choose your own tools.
- Batteries within reason - Ships with essentials (routing, middleware, templates, static files) while keeping advanced concerns in separate shards.
Is Kemal production ready?
Yes. Kemal has been used in production since 2015 with 5M+ downloads.
Does Kemal support WebSocket?
Yes, built-in. No extra dependencies needed.
Can I build a REST API with Kemal?
Yes. JSON handling is built-in. Return hashes or JSON directly from routes.
Does Kemal support the HTTP QUERY method?
Yes. QUERY (RFC 10008) is a safe, idempotent method that carries the query in the request body instead of the URL:
query "/search" do |env|
q = env.params.json["q"]? # or env.params.body for form-encoded queries
search_products(q).to_json
endbefore_query / after_query filters and Kemal::Router#query work like every other verb. A QUERY request that has a body but no Content-Type header is rejected with 400 per the RFC.
What happens when a path exists but the request method doesn't match?
Kemal answers 405 Method Not Allowed with an Allow header listing the methods that path does accept, per RFC 9110 §15.5.6. A path that isn't routed at all is still a 404.
get "/posts" do
"posts"
end
# POST /posts -> 405, Allow: GET, HEAD
# GET /nope -> 404HEAD is included in Allow wherever a GET route exists, since Kemal serves HEAD from the GET route. Register error 405 to customize the body — the Allow header is set before your handler runs, so the handler owns the body but cannot drop the header the RFC requires.
error 405 do |env|
env.response.content_type = "application/json"
{error: "Method not allowed", allow: env.response.headers["Allow"]}.to_json
endNOTE: Kemal::InitHandler presets Content-Type: text/html on every response, so an error handler returning anything else has to set the content type itself.
NOTE: Put authentication in before_all or in middleware rather than in a path-scoped filter like before_get "/admin/*". Path-scoped filters do not run when no route matches, so a wrong-method request answers 405 with Allow — confirming the path exists — without ever reaching the guard.
What does a user see when a route raises?
In the development environment, a page with the exception, its backtrace and source, the response headers and cookies. In every other environment — production, staging, test, or a value Kemal has never heard of — a static "Kemal has encountered an error" page that says nothing about the failure. The environment comes from KEMAL_ENV and defaults to development.
# Show the development page in another environment, or never show it.
Kemal.config.show_exceptions = true
Kemal.config.show_exceptions = falseRegister error 500 to render your own page instead; the setting only decides between Kemal's two built-in ones.
Does Kemal work with any ORM?
Yes. You can use any Crystal ORM or database library. No forced dependencies.
How is Kemal different from Sinatra / Flask / Express?
Kemal compiles to a native binary with Crystal. You get C-like performance, type safety, and single-binary deployment. Sinatra and Flask are interpreted. Express runs on Node's event loop.
If Kemal helps you or your company, consider sponsoring. Your support helps me maintain Kemal and build the Crystal ecosystem full time.
![]() Aurélien Delogu |
![]() David YOTEAU |
![]() Mesut Vatansever |
We love contributions! Please read our Contributing Guide.
To report a security vulnerability, please see our Security Policy.
Special thanks to Manas for their work on Frank.
MIT




