Skip to content

Full-Stack 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

Create a Project#

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

Project Structure#

main.jac#

# 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() -> any {
        has message: str = "Hello from Jac!";

        return <div>
            <h1>{message}</h1>
        </div>;
    }
}

jac.toml#

[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]

Run the App#

Development Mode (with Hot Reload)#

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

Production Mode#

jac start

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


Understanding cl { }#

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() -> any {
        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

File Organization Options#

Option 1: Single File (Small Apps)#

# 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() -> any {
        return <div>App</div>;
    }
}

Option 2: Separate Files (Larger Apps)#

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).


Import Between Files#

Backend Imports#

# api.jac
import from models { User, Todo }

walker get_user {
    can visit with `root entry { report {}; }
}

Frontend Imports#

# main.jac
cl {
    import from "./components/Header.cl.jac" { Header }

    def:pub app() -> any {
        return <div>
            <Header />
            <main>Content</main>
        </div>;
    }
}

Adding npm Packages#

# 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() -> any {
        items = lodash.sortBy(["c", "a", "b"]);
        return <ul>{items.map(lambda i: any -> any { return <li>{i}</li>; })}</ul>;
    }
}

Configuration#

jac.toml Options#

[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"

Verify Setup#

Create this minimal main.jac:

cl {
    def:pub app() -> any {
        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!


Next Steps#