Step 11: Final Integration - Complete App#
** Quick Tip: Each step has two parts. Part 1 shows you what to build. Part 2** explains why it works. Want to just build? Skip all Part 2 sections!
Congratulations! In this final step, you'll see the complete, production-ready todo application with all features integrated!
Part 1: The Complete App#
Complete app.jac File#
Here's your entire application in one file. This is the exact app from the full-stack-with-auth example:
# Full Stack Todo App with Auth and React Router
cl import from react {
useState,
useEffect
}
cl import from "@jac-client/utils" {
Router,
Routes,
Route,
Link,
Navigate,
useNavigate,
jacSignup,
jacLogin,
jacLogout,
jacIsLoggedIn
}
# Backend - Todo Node
node Todo {
has text: str;
has done: bool = False;
}
# Backend - Walkers
walker create_todo {
has text: str;
can create with `root entry {
new_todo = here ++> Todo(text=self.text);
report new_todo ;
}
}
walker read_todos {
can read with `root entry {
visit [-->(`?Todo)];
}
can report_todos with Todo entry {
report here ;
}
}
walker toggle_todo {
can toggle with Todo entry {
here.done = not here.done;
report here ;
}
}
# Frontend Components
cl {
# Navigation
def Navigation() -> any {
let isLoggedIn = jacIsLoggedIn();
let navigate = useNavigate();
def handleLogout(e: any) -> None {
e.preventDefault();
jacLogout();
navigate("/login");
}
if isLoggedIn {
return <nav
style={{
"padding": "12px 24px",
"background": "#3b82f6",
"color": "#ffffff",
"display": "flex",
"justifyContent": "space-between"
}}
>
<div
style={{"fontWeight": "600"}}
>
Todo App
</div>
<div
style={{"display": "flex", "gap": "16px"}}
>
<Link
to="/todos"
style={{"color": "#ffffff", "textDecoration": "none"}}
>
Todos
</Link>
<button
onClick={handleLogout}
style={{
"background": "none",
"color": "#ffffff",
"border": "1px solid #ffffff",
"padding": "2px 10px",
"borderRadius": "4px",
"cursor": "pointer"
}}
>
Logout
</button>
</div>
</nav>;
}
return <nav
style={{
"padding": "12px 24px",
"background": "#3b82f6",
"color": "#ffffff",
"display": "flex",
"justifyContent": "space-between"
}}
>
<div
style={{"fontWeight": "600"}}
>
Todo App
</div>
<div
style={{"display": "flex", "gap": "16px"}}
>
<Link
to="/login"
style={{"color": "#ffffff", "textDecoration": "none"}}
>
Login
</Link>
<Link
to="/signup"
style={{"color": "#ffffff", "textDecoration": "none"}}
>
Sign Up
</Link>
</div>
</nav>;
}
# Login Page
def LoginPage() -> any {
let [username, setUsername] = useState("");
let [password, setPassword] = useState("");
let [error, setError] = useState("");
let navigate = useNavigate();
async def handleLogin(e: any) -> None {
e.preventDefault();
setError("");
if not username or not password {
setError("Please fill in all fields");
return;
}
success = await jacLogin(username, password);
if success {
navigate("/todos");
} else {
setError("Invalid credentials");
}
}
def handleUsernameChange(e: any) -> None {
setUsername(e.target.value);
}
def handlePasswordChange(e: any) -> None {
setPassword(e.target.value);
}
let errorDisplay = None;
if error {
errorDisplay = <div
style={{"color": "#dc2626", "fontSize": "14px", "marginBottom": "10px"}}
>
{error}
</div>;
}
return <div
style={{
"minHeight": "calc(100vh - 48px)",
"display": "flex",
"alignItems": "center",
"justifyContent": "center",
"background": "#f5f5f5"
}}
>
<div
style={{
"background": "#ffffff",
"padding": "30px",
"borderRadius": "8px",
"width": "280px",
"boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
}}
>
<h2
style={{"marginBottom": "20px"}}
>
Login
</h2>
<form
onSubmit={handleLogin}
>
<input
type="text"
value={username}
onChange={handleUsernameChange}
placeholder="Username"
style={{
"width": "100%",
"padding": "8px",
"marginBottom": "10px",
"border": "1px solid #ddd",
"borderRadius": "4px",
"boxSizing": "border-box"
}}
/>
<input
type="password"
value={password}
onChange={handlePasswordChange}
placeholder="Password"
style={{
"width": "100%",
"padding": "8px",
"marginBottom": "10px",
"border": "1px solid #ddd",
"borderRadius": "4px",
"boxSizing": "border-box"
}}
/>
{errorDisplay}
<button
type="submit"
style={{
"width": "100%",
"padding": "8px",
"background": "#3b82f6",
"color": "#ffffff",
"border": "none",
"borderRadius": "4px",
"cursor": "pointer",
"fontWeight": "600"
}}
>
Login
</button>
</form>
<p
style={{
"textAlign": "center",
"marginTop": "12px",
"fontSize": "14px"
}}
>
Need an account?
<Link to="/signup">
Sign up
</Link>
</p>
</div>
</div>;
}
# Signup Page
def SignupPage() -> any {
let [username, setUsername] = useState("");
let [password, setPassword] = useState("");
let [error, setError] = useState("");
let navigate = useNavigate();
async def handleSignup(e: any) -> None {
e.preventDefault();
setError("");
if not username or not password {
setError("Please fill in all fields");
return;
}
result = await jacSignup(username, password);
if result["success"] {
navigate("/todos");
} else {
setError(result["error"] if result["error"] else "Signup failed");
}
}
def handleUsernameChange(e: any) -> None {
setUsername(e.target.value);
}
def handlePasswordChange(e: any) -> None {
setPassword(e.target.value);
}
let errorDisplay = None;
if error {
errorDisplay = <div
style={{"color": "#dc2626", "fontSize": "14px", "marginBottom": "10px"}}
>
{error}
</div>;
}
return <div
style={{
"minHeight": "calc(100vh - 48px)",
"display": "flex",
"alignItems": "center",
"justifyContent": "center",
"background": "#f5f5f5"
}}
>
<div
style={{
"background": "#ffffff",
"padding": "30px",
"borderRadius": "8px",
"width": "280px",
"boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
}}
>
<h2
style={{"marginBottom": "20px"}}
>
Sign Up
</h2>
<form
onSubmit={handleSignup}
>
<input
type="text"
value={username}
onChange={handleUsernameChange}
placeholder="Username"
style={{
"width": "100%",
"padding": "8px",
"marginBottom": "10px",
"border": "1px solid #ddd",
"borderRadius": "4px",
"boxSizing": "border-box"
}}
/>
<input
type="password"
value={password}
onChange={handlePasswordChange}
placeholder="Password"
style={{
"width": "100%",
"padding": "8px",
"marginBottom": "10px",
"border": "1px solid #ddd",
"borderRadius": "4px",
"boxSizing": "border-box"
}}
/>
{errorDisplay}
<button
type="submit"
style={{
"width": "100%",
"padding": "8px",
"background": "#3b82f6",
"color": "#ffffff",
"border": "none",
"borderRadius": "4px",
"cursor": "pointer",
"fontWeight": "600"
}}
>
Sign Up
</button>
</form>
<p
style={{
"textAlign": "center",
"marginTop": "12px",
"fontSize": "14px"
}}
>
Have an account?
<Link to="/login">
Login
</Link>
</p>
</div>
</div>;
}
# Todos Page (Protected)
def TodosPage() -> any {
# Check if user is logged in, redirect if not
if not jacIsLoggedIn() {
return <Navigate to="/login" />;
}
let [todos, setTodos] = useState([]);
let [input, setInput] = useState("");
let [filter, setFilter] = useState("all");
# Load todos on mount
useEffect(
lambda -> None{ async def loadTodos() -> None {
result = root spawn read_todos();
setTodos(result.reports if result.reports else []);
} loadTodos();} ,
[]
);
# Add todo
async def addTodo() -> None {
if not input.trim() {
return;
}
result = root spawn create_todo(text=input.trim());
setTodos(todos.concat([result.reports[0][0]]));
setInput("");
}
# Toggle todo
async def toggleTodo(id: any) -> None {
id spawn toggle_todo();
setTodos(
todos.map(
lambda todo: any -> any{
if todo._jac_id == id {
return {
"_jac_id": todo._jac_id,
"text": todo.text,
"done": not todo.done
};
}
return todo;
}
)
);
}
# Delete todo
async def deleteTodo(id: any) -> None {
#id spawn delete_todo();
setTodos(
todos.filter(lambda todo: any -> bool{ return todo._jac_id != id; } )
);
}
# Filter todos
def getFilteredTodos() -> list {
if filter == "active" {
return todos.filter(
lambda todo: any -> bool{ return not todo.done; }
);
} elif filter == "completed" {
return todos.filter(lambda todo: any -> bool{ return todo.done; } );
}
return todos;
}
filteredTodos = getFilteredTodos();
activeCount = todos.filter(
lambda todo: any -> bool{ return not todo.done; }
).length;
return <div
style={{
"maxWidth": "600px",
"margin": "20px auto",
"padding": "20px",
"background": "#ffffff",
"borderRadius": "8px",
"boxShadow": "0 2px 4px rgba(0,0,0,0.1)"
}}
>
<h1
style={{"marginBottom": "20px"}}
>
My Todos
</h1>
# Add todo input
<div style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}>
<input
type="text"
value={input}
onChange={lambda e: any -> None { setInput(e.target.value); }}
onKeyPress={lambda e: any -> None {
if e.key == "Enter" {
addTodo();
}
}}
placeholder="What needs to be done?"
style={{
"flex": "1",
"padding": "8px",
"border": "1px solid #ddd",
"borderRadius": "4px"
}}
/>
<button
onClick={addTodo}
style={{
"padding": "8px 16px",
"background": "#3b82f6",
"color": "#ffffff",
"border": "none",
"borderRadius": "4px",
"cursor": "pointer",
"fontWeight": "600"
}}
>
Add
</button>
</div>
# Filter buttons
<div style={{"display": "flex", "gap": "8px", "marginBottom": "16px"}}>
<button
onClick={lambda -> None{ setFilter("all");} }
style={{
"padding": "6px 12px",
"background": ("#3b82f6" if filter == "all" else "#e5e7eb"),
"color": ("#ffffff" if filter == "all" else "#000000"),
"border": "none",
"borderRadius": "4px",
"cursor": "pointer",
"fontSize": "14px"
}}
>
All
</button>
<button
onClick={lambda -> None{ setFilter("active");} }
style={{
"padding": "6px 12px",
"background": ("#3b82f6" if filter == "active" else "#e5e7eb"),
"color": ("#ffffff" if filter == "active" else "#000000"),
"border": "none",
"borderRadius": "4px",
"cursor": "pointer",
"fontSize": "14px"
}}
>
Active
</button>
<button
onClick={lambda -> None{ setFilter("completed");} }
style={{
"padding": "6px 12px",
"background": (
"#3b82f6" if filter == "completed" else "#e5e7eb"
),
"color": ("#ffffff" if filter == "completed" else "#000000"),
"border": "none",
"borderRadius": "4px",
"cursor": "pointer",
"fontSize": "14px"
}}
>
Completed
</button>
</div>
# Todo list
<div>
{(<div style={{"padding": "20px", "textAlign": "center", "color": "#999"}}>
No todos yet. Add one above!
</div>) if filteredTodos.length == 0 else (
filteredTodos.map(lambda todo: any -> any {
return <div
key={todo._jac_id}
style={{
"display": "flex",
"alignItems": "center",
"gap": "10px",
"padding": "10px",
"borderBottom": "1px solid #e5e7eb"
}}
>
<input
type="checkbox"
checked={todo.done}
onChange={lambda -> None{ toggleTodo(todo._jac_id);} }
style={{"cursor": "pointer"}}
/>
<span
style={{
"flex": "1",
"textDecoration": (
"line-through" if todo.done else "none"
),
"color": ("#999" if todo.done else "#000")
}}
>
{todo.text}
</span>
<button
onClick={lambda -> None{ deleteTodo(todo._jac_id);} }
style={{
"padding": "4px 8px",
"background": "#ef4444",
"color": "#ffffff",
"border": "none",
"borderRadius": "4px",
"cursor": "pointer",
"fontSize": "12px"
}}
>
Delete
</button>
</div>;
})
)}
</div>
# Stats
{(
<div
style={{
"marginTop": "16px",
"padding": "10px",
"background": "#f9fafb",
"borderRadius": "4px",
"fontSize": "14px",
"color": "#666"
}}
>
{activeCount} {"item" if activeCount == 1 else "items"} left
</div>
)
if todos.length > 0
else None}
</div>;
}
# Home/Landing Page - auto-redirect
def HomePage() -> any {
if jacIsLoggedIn() {
return <Navigate to="/todos" />;
}
return <Navigate to="/login" />;
}
# Main App with React Router
def app() -> any {
return <Router>
<div
style={{"fontFamily": "system-ui, sans-serif"}}
>
<Navigation />
<Routes>
<Route
path="/"
element={<HomePage />}
/>
<Route
path="/login"
element={<LoginPage />}
/>
<Route
path="/signup"
element={<SignupPage />}
/>
<Route
path="/todos"
element={<TodosPage />}
/>
</Routes>
</div>
</Router>;
}
}
Running the App#
-
Save the code to
app.jac -
Start the server:
-
Open in browser:
-
Test it out:
- Create an account (signup)
- Login
- Add some todos
- Toggle them complete/incomplete
- Filter (All/Active/Completed)
- Delete todos
- Logout and login again - your todos persist!
** You did it!** You've built a complete full-stack app. The rest of this page explains what you built and what to do next.
Part 2: What You Built#
Features Checklist#
Authentication: - User signup - User login - Logout - Session persistence - Protected routes
Todo Management: - Create todos - Mark as complete/incomplete - Delete todos - Filter by status (all/active/completed) - Item counter - Empty state handling
UI/UX: - Responsive design - Modern styling - Form validation - Error handling - Loading states - Smooth navigation
Backend: - Data persistence with walkers - User isolation (each user sees only their data) - Graph-based data structure - Automatic API endpoints
Technology Stack#
Frontend:
- React (via Jac's cl blocks)
- React Router (for navigation)
- Inline CSS styling
- JSX syntax
Backend: - Jac walkers (backend functions) - Jac nodes (data structures) - Graph database (automatic) - Built-in authentication
Architecture: - Single-page application (SPA) - Client-side routing - RESTful-like walker calls - Full-stack in one language
File Structure#
Your entire app:
├── app.jac (735 lines)
├── Backend (nodes + walkers)
├── Frontend (React components)
└── Routes (navigation)
That's it! Just one file!
Code Organization#
app.jac
├── Backend Section
│ ├── node Todo (data model)
│ └── Walkers (create, read, toggle, delete)
│
└── Frontend Section (cl block)
├── Navigation component
├── LoginPage component
├── SignupPage component
├── TodosPage component
├── HomePage component (redirects)
└── app function (router setup)
What's Next?#
You've completed the tutorial! Here are some ideas to continue learning:
1. Enhance Your App#
Easy additions: - Edit todo text - Add due dates - Priority levels (high/medium/low) - Todo categories/tags - Search functionality
Medium difficulty: - Drag-and-drop reordering - Dark mode toggle - Keyboard shortcuts - Undo/redo - Export/import todos
Advanced features: - Real-time collaboration - Recurring todos - Notifications - Attach files to todos - Share lists with others
2. Improve the UI#
Styling: - Add CSS animations - Use a CSS framework (Tailwind CSS) - Better mobile responsiveness - Custom color themes - Icons library (React Icons)
UX improvements: - Smooth transitions - Better loading states - Toast notifications - Confirmation dialogs - Keyboard navigation
3. Deploy Your App#
Deployment options: - Jac Cloud (easiest) - Vercel - Netlify - Digital Ocean - AWS
Steps:
4. Learn Advanced Jac Features#
Explore: - AI features with byLLM - Complex graph structures - Advanced walker patterns - Multi-file organization - Testing strategies - Performance optimization
5. Build Something New#
Apply what you learned: - Blog platform - E-commerce store - Social media app - Project management tool - Chat application - Portfolio website
Resources#
Official Documentation: - Jac Documentation - Jac Examples - React Docs (underlying framework)
Community: - Jac Discord/Forum - GitHub Issues - Stack Overflow (tag: jac-lang)
Tutorials: - Jac AI Features - Advanced Graph Patterns - Deployment Guides - Best Practices
What You Learned#
Looking back at all 11 steps:
- Project setup and structure
- Components and props
- Styling with inline CSS
- Building complex UIs
- State management with useState
- Event handlers
- Side effects with useEffect
- Backend with walkers and nodes
- User authentication
- Client-side routing
- Complete full-stack integration
Key concepts mastered: - Full-stack development in one language - React component patterns - State management - Graph-based data storage - Authentication and authorization - Client-side routing - Async/await patterns - Form handling - Error handling
Congratulations!#
You built a complete, production-ready full-stack application from scratch!
What makes this special: - 735 lines of code (compared to 2000+ in traditional stacks) - One language (compared to 3-4: JavaScript, Python, SQL, HTML/CSS) - One file (compared to dozens of files) - Zero configuration (no webpack, babel, etc.) - Built-in auth (no OAuth setup needed) - Automatic backend (no Express/Flask setup)
You're now ready to build amazing full-stack applications with Jac!
Share Your Success!#
Built something cool? Share it: - Tag #JacLang on social media - Contribute to Jac examples - Write a blog post - Help others learn
Thank you for completing this tutorial!
Happy coding with Jac!