app

Version, currently main branch1 version
  • main branchlatestOct 9, 2026

github.com/stijnwtf/crystal-api-starter

๐Ÿ’Ž Modern Crystal JSON API starter: Kemal, typed JSON, specs, Ameba linting and a static binary in a FROM scratch Docker image.

1 stars
0 dependents
License: MIT

Installation

# Add this to your shard.yml
dependencies:
  app:
    github: stijnwtf/crystal-api-starter
    branch: main

main is a branch, not a release, so this tracks it rather than pinning a version.

Then run:

shards install

shard.yml

Crystal
>= 1.21.0
License
MIT
Target
  • app from src/app.cr

Dependencies

Runtime Dependencies

  • kemal~> 1.14github: kemalcr/kemal

Development Dependencies

  • ameba*github: crystal-ameba/amebadev
  • spec-kemal*github: kemalcr/spec-kemaldev

README

What's inside

๐Ÿš€ KemalFast, Sinatra-style routing
๐Ÿงฑ Typed JSONJSON::Serializable models and request structs, validation with clear errors
๐Ÿ”’ Thread-safe storeIn-memory, mutex-guarded, easy to swap for Postgres or SQLite
๐Ÿงช SpecsRequest specs with spec-kemal
๐Ÿงน LintingAmeba + crystal tool format
๐Ÿณ DockerStatic binary in a FROM scratch image
๐Ÿค– CIFormat, lint, specs and Docker build on every push

Quick start

git clone https://github.com/stijnwtf/crystal-api-starter my-api
cd my-api
shards install
crystal run src/app.cr     # http://localhost:3000

Needs Crystal 1.21+ (brew install crystal).

$ curl -X POST localhost:3000/tasks -d '{"title":"Try Crystal"}'
{"id":1,"title":"Try Crystal","completed":false,"created_at":"2026-10-09T09:29:56Z"}

Commands

CommandDoes
make devRun with auto-restart (watchexec)
make testRun specs
make lintRun Ameba
make formatFormat code
make buildOptimized binary in bin/app
make dockerBuild the production image

API

MethodPathBodyResponse
GET/health{"status","version"}
GET/tasksTask[]
GET/tasks/:idTask or 404
POST/tasks{"title"}201 Task, 400 or 422
PATCH/tasks/:id{"title"?, "completed"?}Task, 404 or 422
DELETE/tasks/:id204 or 404

Structure

src/
โ”œโ”€โ”€ app.cr            entry: config, JSON error handlers, server start
โ”œโ”€โ”€ store.cr          thread-safe in-memory store
โ”œโ”€โ”€ models/task.cr    Task model, request bodies, validation
โ””โ”€โ”€ routes/           one file per resource
spec/                 request specs

Add a resource: create src/models/thing.cr and src/routes/things.cr. Both folders are required with a glob, so no wiring needed.

Deploy

docker build -t my-api .
docker run -p 3000:3000 my-api

Configure the port with PORT (default 3000).

If this saved you time, a โญ๏ธ helps others find it.

License

MIT