JCC Express

The Basics

API Resources

Introduction

API resources transform your Eloquent models into JSON the way you want clients to see them — the same role as Laravel's JsonResource. They live in app/Http/Resources/ and extend the framework's JsonResources base class.

When a route or controller returns a resource instance, the framework serializes it through toJSON() (same as JSON.stringify / res.json()).


Generating resources

Bash
bun artisanNode make:resource Post
bun artisanNode make:resource PostResources
bun artisanNode make:resource Api/V1/Post

The command creates app/Http/Resources/{Name}Resources.ts (or a nested path such as app/Http/Resources/Api/V1/PostResources.ts). You may pass Post, PostResource, or PostResources — the class name is always normalized to {Name}Resources.

Generated files respect FILE_EXT in .env (ts or js).


Defining a resource

Implement toArray() and return the shape you want. Model attributes are available on $this / this (delegated from the wrapped model, like Laravel's DelegatesToResource):

TypeScript
import { JsonResources } from "jcc-express-mvc/lib/Resources";
import { UserResources } from "./UserResources";

export class PostResources extends JsonResources {
  declare id: number;
  declare body: string;

  toArray(): Record<string, unknown> {
    return {
      id: this.id,
      body: this.body,
      user: UserResources.make(this.whenLoaded("user")),
    };
  }
}

Use declare for fields you read via this.* so TypeScript knows they exist. Runtime values come from the model.


Returning resources

Single model — eager-load relations you expose:

TypeScript
const post = await Post.where({ id: 1 }).with("user").first();
return new PostResources(post);
// or
return PostResources.make(post);

Collection:

TypeScript
const posts = await Post.with("user").get();
return PostResources.collection(posts);

Paginator — pass the paginator object from Model.paginate(); the collection wrapper adds data, meta, and links:

TypeScript
const page = await Post.with("user").paginate(15);
return PostResources.collection(page);

Conditional attributes

Laravel-style helpers omit keys when a value is not present:

MethodPurpose
whenLoaded("relation")Include a relation only when eager loaded
when(condition, value)Include a value when a condition is true
mergeWhen(condition, { ... })Merge attributes when a condition is true
whenHas("attribute")Include when the model has a non-null attribute
relationLoaded("relation")Check if a relation was eager loaded

Nested resources:

TypeScript
user: UserResources.make(this.whenLoaded("user")),

If user was not loaded with .with("user"), the user key is omitted from the JSON.


Framework layout

Import resource classes from your application (app/Http/Resources/) after running bun artisanNode make:resource.

TypeScript
import { PostResources } from "@/Http/Resources/PostResources";
import { JsonResources } from "jcc-express-mvc/lib/Resources";

Use JsonResources and ResourceCollection when building custom transformers. See Artisan Node for make:resource.


  • Response — JSON responses and route return values
  • ArtisanNodemake:resource and other generators
  • Pagination — paginated collections with resources