Skip to content

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#

  1. Save the code to app.jac

  2. Start the server:

    jac serve app.jac
    
  3. Open in browser:

    http://localhost:8000/page/app
    
  4. Test it out:

  5. Create an account (signup)
  6. Login
  7. Add some todos
  8. Toggle them complete/incomplete
  9. Filter (All/Active/Completed)
  10. Delete todos
  11. 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:

# Install Jac Cloud
pip install jac-cloud

# Deploy
jac cloud deploy app.jac

# Your app is now live!

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:

  1. Project setup and structure
  2. Components and props
  3. Styling with inline CSS
  4. Building complex UIs
  5. State management with useState
  6. Event handlers
  7. Side effects with useEffect
  8. Backend with walkers and nodes
  9. User authentication
  10. Client-side routing
  11. 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!