JCC Express

The Basics

JCC Blade

Introduction

JCC Blade is the Laravel-style template engine included with jcc-express-mvc. Templates use the extension you configure (often .blade.html under resources/views). Render them with res.render / view() — see Views for app wiring.

Everything below is directive usage: copy the patterns into your templates and adjust names to match your controller data and view paths.


Output

Escaped (safe for user-controlled text):

blade
<p>{{ title }}</p>
<p>{{ user . name }}</p>

Raw HTML (only for trusted content):

blade
<div>{!! trustedHtml !!}</div>

Dot paths may include spaces around . if you prefer. Inside {{ }} you can use whitelisted methods (for example {{ items.join(', ') }}, {{ name.toUpperCase() }}).


Layouts: @extends, @section, @yield

Child view (e.g. resources/views/posts/index.blade.html):

blade
@extends("layouts.app")

@section("title", "Posts")

@section("content")
  <h1>Posts</h1>
  @foreach (posts as post)
    <article>{{ post . title }}</article>
  @endforeach
@endsection

Layout (e.g. resources/views/layouts/app.blade.html):

blade
<!DOCTYPE html>
<html>
<head>
  <title>@yield("title")</title>
  @stack("styles")
</head>
<body>
  @include("partials.nav")
  <main>
    @yield("content")
  </main>
  @stack("scripts")
</body>
</html>

Use dot paths without the file extension in quotes: "layouts.app"layouts/app.blade.html under your views root.


Partials: @include and variants

blade
@include("partials.alert")

@include("partials.card", { title: "Hello", body: "World" })

Conditional includes:

blade
@includeIf("partials.optional-banner")

@includeWhen(showSidebar, "partials.sidebar")

@includeUnless(hideFooter, "partials.footer")

Paths use the same dot notation as res.render.


Stacks: @push, @prepend, @stack

From any view that extends or includes the layout:

blade
@push("scripts")
  <script src="/extra.js"></script>
@endpush

@prepend("styles")
  <link rel="stylesheet" href="/critical.css" />
@endprepend

The layout prints them with @stack("scripts") and @stack("styles") where those names match.


Conditionals

@if / @elseif / @else / @endif

blade
@if (count > 0)
  <p>You have {{ count }} items.</p>
@elseif (count === 0)
  <p>Nothing here.</p>
@else
  <p>Unknown.</p>
@endif

Conditions may use comparisons (==, !=, ===, >, <, >=, <=) and dotted paths such as user.role.

@unless

blade
@unless (loggedIn)
  <a href="/login">Log in</a>
@endunless

@isset / @empty

blade
@isset (subtitle)
  <h2>{{ subtitle }}</h2>
@endisset

@empty (items)
  <p>No items.</p>
@endempty

@switch

blade
@switch (status)
  @case ("draft")
    <span>Draft</span>
    @break
  @case ("published")
    <span>Live</span>
    @break
  @default
    <span>Unknown</span>
@endswitch

@ternary

The condition must be a simple identifier or dotted path (no function calls). The branches are parsed as Blade/HTML.

blade
@ternary(isSale ? "<span>Sale</span>" : "<span>Full price</span>")

Loops

@foreach

The loop object exposes index (0-based), first, last, count, even, and odd. The current item also gets loopIndex (same as loop.index).

blade
<ul>
  @foreach (users as user)
    <li class="{{ loop . even ? "even" : "odd" }}">
      {{ loop . index }}{{ user . name }}
    </li>
  @endforeach
</ul>

@forelse (empty fallback)

blade
@forelse (messages as msg)
  <p>{{ msg . text }}</p>
@empty
  <p>No messages.</p>
@endforelse

@for

blade
@for (i, 0, 5)
  <span>{{ i }}</span>
@endfor

Optional step: @for (i, 0, 10, 2).

@while

@while (condition)@endwhile re-evaluates condition against the same template data each time (hard cap 10 000 iterations). The context is not updated between passes, so most repeating markup should use @for or @foreach instead.


Environment

blade
@production
  <script src="/analytics.js"></script>
@endproduction

@env("local", "development")
  <p>Dev toolbar</p>
@endenv

Auth helpers

The engine looks for an Auth property on the template data (same object you pass to res.render / view(), merged with res.locals). It is not wired automatically to req.user; expose something like Auth: req.user (or your session user) when rendering, or use a view composer/middleware that sets res.locals.Auth.

blade
@auth
  <p>Hello, {{ Auth . name }}</p>
@endauth

@auth("admin")
  <a href="/admin">Admin</a>
@endauth

@guest
  <a href="/login">Log in</a>
@endguest

@guest runs when Auth is empty or missing. @auth("role") matches Auth.role, Auth.roleType, or Auth.isAdmin depending on your shape.


Forms: @csrf and @method

Use with csrf() and methodSpoofing in your HTTP kernel (CSRF-protection.md, Middleware.md).

blade
<form method="POST" action="/posts/1">
  @csrf
  @method("PUT")
  <input type="text" name="title" value="{{ old . title }}" />
  <button type="submit">Save</button>
</form>

old is set on res.locals when the framework flashes previous input after validation errors (Request.md). @method accepts the verb in quotes, e.g. "PUT", "PATCH", "DELETE".


JSON, assets, Vite, Inertia

blade
<script type="application/json" id="payload">@json(config)</script>

<link rel="stylesheet" href="{{ assets("build/app.css") }}" />

@vite(["resources/js/app.jsx"])

@inertia

For Vite scripts, public/hot, the manifest, and @viteReactRefresh, see Asset-bundling.md. For Inertia setup and SSR, see Getting-Started/Frontend.md.


Raw blocks: @verbatim

Content inside is not parsed as Blade (useful for {{ in docs or client-side templates):

blade
@verbatim
  <p>Literal {{ not parsed }}</p>
@endverbatim

@js@endjs (server-side)

@js runs JavaScript on the server inside a node:vm context. The template’s data object is copied into a sandbox, the block runs, then properties from the sandbox are merged back into the live template context. The block produces no HTML by itself—use it to compute values you reference later with {{ }}.

blade
@js
  total = (items || []).length;
  heading = total > 0 ? "You have " + total + " items" : "Empty";
@endjs

<h1>{{ heading }}</h1>

The engine strips leading const / let / var from the block so assignments tend to land on the sandbox (see handleJs in index.ts). Errors are logged to the console and do not throw out of the template by default.

Security: this is arbitrary code execution. Only use @js in templates you fully trust (your own view files), never in user-supplied strings.


@script (client-side markup)

@script@endscript wraps the inner content in a <script></script> tag. The inner text is still parsed as Blade, so you can inject values with {{ }}:

blade
@script
  const userId = {{ user . id }};
  console.log("User", userId);
@endscript

@script("path") (quoted path) pulls a file from resources/js/, using dot segments as directories. If the path has no extension, .jsx is appended (for example @script("pages.dashboard")resources/js/pages/dashboard.jsx). The resolved asset is passed through the same asset pipeline helper as other script tags (see handleScriptPath in the engine).


Errors and performance

Template parse/runtime errors may throw TemplateError with line/column when available. In production, merged layouts for @extends are cached until template files change (mtime).