Skip to content
Kona

API Resources

An API resource is a dedicated transformer between your domain object and the JSON map you send to clients. Keeping that mapping in one class avoids leaking internal fields and keeps list vs detail payloads consistent.

Generate a stub with ./grind make:resource User (--collection adds a collection helper).

Subclass JsonResource and implement toArray. By default resolve / toResponse wrap the map under a "data" key. Call JsonResource.withoutWrapping() (usually once at boot) to disable that, or JsonResource.wrap("payload") to rename it.

class UserResource(
private val id: Int,
private val name: String,
private val email: String?,
model: Any? = null,
) : JsonResource(model) {
override fun toArray(request: Request): Map<String, Any?> =
mapOf(
"id" to id,
"name" to name,
"email" to `when`(email != null, email),
"admin" to mergeWhen(request.header("X-Role") == "admin", mapOf("role" to "admin")),
"posts" to whenLoaded("posts"),
)
}
router.get("/users/{id}") { request ->
UserResource(1, "Ada", "ada@example.com").toResponse(request)
}
Helper Behavior
when(condition, value) Omit the key when the condition is false (MissingValue)
mergeWhen(condition, map) Flatten map into the parent when true
whenLoaded("relation") Include a relation only if the underlying model already loaded it (relationLoaded / getRelation — works with Cadence models without a hard dependency)
additional(map) / with(request) Extra top-level keys beside the wrapped payload

toResponse wraps resolve in Response.json(...). Call resolve(request) when you need the map without building a response yet.

Wrap many resources with JsonResource.collection(...). By default the payload is { "data": [ ... ] }. Change or remove the wrapper with wrap:

val payload = JsonResource.collection(
listOf(UserResource(1, "Ada", null), UserResource(2, "Grace", null)),
).wrap("users").toResponse(request)

Pass wrap(null) for a bare JSON array.

Register an API resource controller, then return resources from actions:

router.group(prefix = "/api", name = "api.") {
apiResource("projects", ProjectApiController(app))
}

Typical flow: controller loads a model (optionally with relations) → builds a resource → returns toResponse. Pair with Controllers, resource / apiResource routes, and named links from URL generation.