crystalapi
CrystalAPI
A task management REST API built with Crystal 1.11 + Kemal — a compiled language with Ruby-like syntax delivering native performance. Features JWT authentication, bcrypt password hashing, and a thread-safe in-memory store.
Features
- Crystal + Kemal — compiled native binary, Ruby-like syntax, zero-overhead HTTP framework
- JWT auth —
POST /auth/register+POST /auth/loginreturn signed tokens - Bcrypt passwords —
Crypto::Bcrypt::Passwordfor secure hashing - Task CRUD — full create/read/update/delete with status and priority filtering
- Task stats — aggregated counts by status and priority
- CORS middleware —
before_allsets headers for all routes - Thread-safe store —
Mutex-protected in-memoryMemoryStoresingleton - Enums —
TaskStatus(todo/in_progress/done) andTaskPriority(low/medium/high)
Tech Stack
| Component | Technology |
|---|---|
| Language | Crystal 1.11 |
| HTTP | Kemal ~> 1.4 |
| Auth | JWT ~> 1.6 + Bcrypt |
| Storage | In-memory (Mutex-protected) |
| Serialization | JSON::Serializable |
Quick Start
# Requires Crystal 1.11+ and shards
shards install
crystal run src/crystalapi.cr
# Or build a native binary
shards build --release
./bin/crystalapi
# Server at http://localhost:3000
PORT=8080 crystal run src/crystalapi.cr # custom port
API Endpoints
GET /health Health check
GET / API info
POST /auth/register { username, email, password }
POST /auth/login { email, password } → { token, user }
GET /auth/me (Bearer) current user
GET /tasks (Bearer) ?status=todo&priority=high
GET /tasks/stats (Bearer) counts by status/priority
GET /tasks/:id (Bearer)
POST /tasks (Bearer) { title, description, status, priority }
PUT /tasks/:id (Bearer) partial update
DELETE /tasks/:id (Bearer)
Project Structure
project-87-crystalapi/
├── shard.yml
├── shard.lock
└── src/
├── crystalapi.cr # Kemal routes, middleware, Kemal.run
├── models/
│ └── task.cr # Task, User structs + enums
├── store/
│ └── memory_store.cr # Singleton in-memory store (Mutex)
└── auth/
└── jwt_auth.cr # JWT encode/decode, require_auth helper
Key Crystal Patterns
# JSON::Serializable struct
struct Task
include JSON::Serializable
property id : Int64
property title : String
property status : TaskStatus
end
# Mutex-protected singleton
class MemoryStore
@@instance = new
@mutex = Mutex.new
def self.instance
@@instance
end
def create_task(owner_id, title, desc, status, priority)
@mutex.synchronize do
# thread-safe mutation
end
end
end
# JWT auth middleware
def self.require_auth(env)
token = env.request.headers["Authorization"]?.try(&.sub("Bearer ", "")) || ""
payload, _ = JWT.decode(token, SECRET, JWT::Algorithm::HS256)
user_id = payload["sub"].as_i64
MemoryStore.instance.get_user(user_id) || raise "Unauthorized"
end
# Kemal route with next for early return
post "/tasks" do |env|
user = CrystalAPI.require_auth(env)
title = env.params.json["title"]?.try(&.as_s) || ""
if title.empty?
env.response.status_code = 400
next %({"error":"title is required"})
end
task = MemoryStore.instance.create_task(user.id, title, nil, TaskStatus::Todo, TaskPriority::Medium)
env.response.status_code = 201
task.to_json
end
Example Usage
# Register
curl -X POST http://localhost:3000/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"alice","email":"alice@example.com","password":"secret123"}'
# Login
TOKEN=$(curl -s -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"alice@example.com","password":"secret123"}' | jq -r .token)
# Create task
curl -X POST http://localhost:3000/tasks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Ship feature","priority":"high"}'
# List tasks
curl http://localhost:3000/tasks?status=todo \
-H "Authorization: Bearer $TOKEN"
Repository
crystalapi
Owner
Statistic
- 0
- 0
- 0
- 0
- 2
- about 2 hours ago
- April 29, 2026
License
Links
Synced at
Tue, 02 Jun 2026 07:25:49 GMT
Languages