Skip to content

Project Setup

Create a Jac project with frontend and backend in one codebase.

Prerequisites

  • Completed: Hello World
  • Familiar with: HTML/CSS basics, React concepts helpful
  • Install: pip install jaseci
  • Time: ~15 minutes

jac create --use client myapp
cd myapp

This creates:

myapp/
├── jac.toml # Configuration
├── main.jac # Entry point (frontend + backend)
├── README.md # Project readme
├── components/ # Reusable UI components
│ └── Button.cl.jac # Example button component
├── assets/ # Static assets (images, fonts)
├── .jac/ # Build artifacts (gitignored)
└── .gitignore # Git ignore rules

# Backend code (nodes, walkers)
node Todo {
has title: str;
has done: bool = False;
}
walker:pub get_todos {
can fetch with Root entry {
for todo in [-->](?:Todo) {
report todo;
}
}
}
# Frontend code (inside cl block)
cl {
def:pub app() -> JsxElement {
has message: str = "Hello from Jac!";
return <div>
<h1>{message}</h1>
</div>;
}
}
[project]
name = "myapp"
version = "1.0.0"
description = "Jac client application"
entry-point = "main.jac"
[dependencies]
[dependencies.npm]
jac-client-node = "1.0.4"
[dependencies.npm.dev]
"@jac-client/dev-deps" = "1.0.0"
[dev-dependencies]
watchdog = ">=3.0.0"
[serve]
base_route_app = "app"
[plugins.client]

jac start --dev

This starts:

  • Vite dev server on port 8000 (open in browser)
  • API server on port 8001 (proxied via Vite)
  • File watcher for .jac files

Open http://localhost:8000/cl/app

jac start

Open http://localhost:8000/cl/app


The cl { } block marks frontend (client) code:

# This is backend code (runs on server)
walker api_endpoint {
can visit with Root entry { report {}; }
}
# This is frontend code (runs in browser)
cl {
def:pub MyComponent() -> JsxElement {
return <div>I run in the browser</div>;
}
}

Key rules:

  • cl { } code compiles to JavaScript/React
  • def:pub exports functions (like React components)
  • app() is the required entry point

# main.jac - everything in one file
# Backend
node User { has name: str = ""; }
walker get_user {
can visit with Root entry { report {}; }
}
# Frontend
cl {
def:pub app() -> JsxElement {
return <div>App</div>;
}
}
myapp/
├── main.jac # Entry point
├── models.jac # Backend nodes
├── api.jac # Backend walkers
├── components/
│ ├── Header.cl.jac # Frontend component
│ └── Footer.cl.jac # Frontend component
└── pages/
├── Home.cl.jac # Frontend page
└── About.cl.jac # Frontend page

Note: .cl.jac files are automatically client-side (no cl { } needed).


# api.jac
import from models { User, Todo }
walker get_user {
can visit with Root entry { report {}; }
}
# main.jac
cl {
import from "./components/Header.cl.jac" { Header }
def:pub app() -> JsxElement {
return <div>
<Header />
<main>Content</main>
</div>;
}
}

# Add a package
jac add --npm lodash
# Add dev dependency
jac add --npm --dev @types/react
# Install all dependencies
jac add --npm

Or in jac.toml:

[dependencies.npm]
lodash = "^4.17.21"
axios = "^1.6.0"

Then use in frontend:

cl {
import lodash;
def:pub app() -> JsxElement {
items = lodash.sortBy(["c", "a", "b"]);
return <ul>{items.map(lambda i: any -> any { return <li>{i}</li>; })}</ul>;
}
}

[project]
name = "myapp"
version = "0.1.0"
entry-point = "main.jac"
[plugins.client]
# Client-specific config
[plugins.client.configs.postcss]
plugins = ["tailwindcss", "autoprefixer"]
[dependencies]
# Python packages
[dependencies.npm]
# npm packages
[dev-dependencies]
watchdog = ">=3.0.0"

Create this minimal main.jac:

cl {
def:pub app() -> JsxElement {
has count: int = 0;
return <div style={{"textAlign": "center", "marginTop": "50px"}}>
<h1>Jac Full-Stack</h1>
<p>Count: {count}</p>
<button onClick={lambda -> None { count = count + 1; }}>
Increment
</button>
</div>;
}
}

Run jac start --dev and open http://localhost:8000/cl/app

Click the button - the count should increase!