Skip to main content

kemal-json

Building JSON APIs with Kemal using built-in response helpers and established project patterns.

معلومات المصدر

المستودع
kemalcr/kemal
آخر نشاط في المصدر
٢٤ أغسطس ٢٠٢٦ في ١١:٢٨
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٣٬٩٢٣
التفرعات
٢٠٦

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
kemal-json
description
Building JSON APIs with Kemal using built-in response helpers and established project patterns.
license
MIT
# Kemal JSON API Development This skill provides expert guidance on building robust JSON APIs with Kemal, establishing the canonical patterns for JSON response helpers and strictly following patterns from [`kemal-by-example/json-api`](https://github.com/sdogruyol/kemal-by-example/tree/master/json-api). ## Version Notes - `env.json` and `env.status` response helpers (symbol or integer statuses): since Kemal 1.10. - Malformed request bodies (e.g. invalid JSON) are answered with `400 Bad Request` since Kemal 1.13.0; earlier releases return `500`. ## Core Mandates - **Dependencies:** Always `require "json"`. - **Built-in Context Response Helpers (Kemal 1.10+):** Use Kemal's built-in `env.json` and status helpers for API responses: ```crystal # Basic JSON response (sets Content-Type to application/json; charset=utf-8 automatically) get "/api/users" do |env| env.json({users: %w[alice bob]}) end # Symbol or integer HTTP status code with JSON: post "/api/users" do |env| env.status(:created).json({id: 1, name: "Alice"}) end # Early halt with JSON: get "/api/protected" do |env| halt env.status(:unauthorized).json({error: "Unauthorized"}) unless authenticated?(env) end ``` - **Project JsonResponse Helpers (Custom Pattern):** When using standard project helpers for responses (found in `JsonApi::JsonResponse`): ```crystal module JsonApi module JsonResponse extend self def json(env : HTTP::Server::Context, status : Int32, payload : String) env.response.status_code = status env.response.content_type = "application/json; charset=utf-8" payload end def error(env : HTTP::Server::Context, status : Int32, message : String) json(env, status, {"error" => message}.to_json) end end end ``` - **Safe JSON Body Parsing:** Use `env.params.raw_body` or `read_json_object(env)` to safely parse JSON bodies and handle exceptions: ```crystal private def read_json_object(env : HTTP::Server::Context) : Hash(String, JSON::Any)? raw = env.params.raw_body return nil if raw.strip.empty? parsed = JSON.parse(raw) parsed.as_h? rescue JSON::ParseException nil end ``` - **Serialization:** Models should provide a `to_h` method. Use `model.to_h` or `model.to_h.to_json` for responses. ## Patterns from Source Code ### API Routes with Full CRUD (json-api/src/routes/api.cr) ```crystal require "json" require "../helpers/json_response" require "../models/note" private def read_json_object(env : HTTP::Server::Context) : Hash(String, JSON::Any)? raw = env.params.raw_body return nil if raw.strip.empty? parsed = JSON.parse(raw) parsed.as_h? rescue JSON::ParseException nil end private def note_id(env : HTTP::Server::Context) : Int64? env.params.url["id"]?.try(&.to_i64?) end private def string_field(h : Hash(String, JSON::Any), key : String) : String? h[key]?.try(&.as_s?) end get "/api/notes" do |env| list = Note.all.map(&.to_h) env.json({"notes" => list}) end get "/api/notes/:id" do |env| id = note_id(env) unless id halt env.status(:bad_request).json({"error" => "invalid id"}) end note = Note.find(id) if note env.json(note.to_h) else env.status(:not_found).json({"error" => "note not found"}) end end post "/api/notes" do |env| obj = read_json_object(env) unless obj halt env.status(:bad_request).json({"error" => "expected JSON object body"}) end title = string_field(obj, "title").try(&.strip) || "" if title.empty? halt env.status(:unprocessable_entity).json({"error" => "title is required"}) end body = string_field(obj, "body").try(&.strip) || "" new_id = Note.create(title, body) note = Note.find(new_id) if note env.status(:created).json(note.to_h) else env.status(:internal_server_error).json({"error" => "failed to load created note"}) end end delete "/api/notes/:id" do |env| id = note_id(env) unless id halt env.status(:bad_request).json({"error" => "invalid id"}) end note = Note.find(id) unless note halt env.status(:not_found).json({"error" => "note not found"}) end note.delete env.status(:no_content) end ``` ## Best Practices - **Use Context Response Helpers:** Leverage `env.json` and `env.status(...)` for idiomatic, chainable response handling. - **Consistent Error Format:** Always return a JSON object with an `error` key for failure states. - **Safe Field Access:** Use `obj[key]?.try(&.as_s?)` or similar safe accessors for fields in parsed JSON hashes. - **Input Validation:** Rigorously validate required fields and types before processing the request. - **Status Symbols:** Use standard status symbols like `:ok`, `:created`, `:no_content`, `:bad_request`, `:unauthorized`, `:not_found`, `:unprocessable_entity`. ## When to Use - When developing or refactoring API routes that return JSON. - When handling JSON body parameters from client requests. - When implementing standardized JSON error handling.
عرض على GitHub