Basil Development
When to use this skill
Use when:
- •building Basil web apps,
- •configuring basil.yaml,
- •testing handlers with curl,
- •working with @basil/http
- •working with @basil/auth context,
- •debugging routing and Parts.
Use this skill when you need to:
What is Basil?
Basil is a web framework for Parsley (the language):
- •Parsley = the language (like JavaScript)
- •Basil = the web framework (like Express/Rails)
Basil provides HTTP server, routing, sessions, auth, database, interactive components (Parts), and asset bundling.
Quick Start
bash
# Initialize project ./basil --init myapp && cd myapp # Run dev server (auto-reload, detailed errors, dev tools at /__dev/log) ./basil --dev # Test curl http://localhost:8080/
Core Concepts
1. Handlers
Parsley files that handle HTTP requests.
parsley
// site/index.pars
<html>
<body>
<h1>"Welcome!"</h1>
<p>`Search: {@params.q ?? 'none'}`</p>
</body>
</html>
2. Configuration (basil.yaml)
yaml
server: port: 8080 site: ./site # Filesystem routing: site/about.pars → /about sqlite: ./data.db # Database session: secret: "your-32-char-secret" # Required for sessions
3. Parts (Interactive Components)
.part files with functions that return HTML. Update without page reload.
parsley
// parts/counter.part
export default = fn(props) {
let count = props.count ?? 0
<div>
<p>`Count: {count}`</p>
<button part-click="increment" part-count={count + 1}>"++"</button>
</div>
}
export increment = fn(props) {
default(props) // Re-render with new count
}
Usage:
parsley
<Part src={@~/parts/counter.part} count={0}/>
Important: Parts need routes in basil.yaml:
yaml
routes:
- path: /parts/counter.part
handler: ./parts/counter.part
Essential Imports
HTTP Context (@basil/http)
parsley
let {request, response, method} = import @basil/http
let {redirect} = import @std/api
@params // URL/form params: {id: "123"}
method // "GET", "POST", etc.
request.body // Request body (JSON auto-parsed)
request.path // "/api/users/123"
response.status = 404
redirect("/dashboard") // Returns redirect response
Auth Context (@basil/auth)
parsley
let {session, user} = import @basil/auth
// Sessions
session.set("cart", ["item1", "item2"])
let cart = session.get("cart", [])
session.flash("success", "Saved!") // Show-once message
// Current user (null if not logged in)
if (user) {
user.email
user.role // "admin", "user", etc.
}
Magic Variables
parsley
@params // URL/form parameters @DB // Server database (configured in basil.yaml) @now // Current datetime @env.HOME // Environment variables
Database Operations
parsley
// Use @DB magic variable for database access
let id = 123
let user = @DB <=?=> `SELECT * FROM users WHERE id = {id}` // One row
let users = @DB <=??=> "SELECT * FROM users" // All rows
let name = "Alice"
let result = @DB <=!=> `INSERT INTO users (name) VALUES ('{name}')` // Mutation
// See docs/basil/reference.md for complete database API
Common Handler Patterns
Database Query Handler
parsley
let users = @DB <=??=> "SELECT * FROM users ORDER BY name"
<html>
<body>
<ul>
for (user in users) {
<li>user.name</li>
}
</ul>
</body>
</html>
JSON API Handler
parsley
let {method} = import @basil/http
if (method == "GET") {
let users = @DB <=??=> "SELECT id, name FROM users"
{users: users}
} else if (method == "POST") {
let result = @DB <=!=> `INSERT INTO users (name) VALUES ('{@params.name}')`
let id = @DB.lastInsertId()
{id: id}
}
Form Handler
parsley
let {method, request} = import @basil/http
let {session} = import @basil/auth
let {redirect} = import @std/api
if (method == "POST") {
let form = request.form
// Process form...
session.flash("success", "Saved!")
redirect("/")
} else {
<form method="POST">
<input name="email" required/>
<button>"Submit"</button>
</form>
}
Testing with curl
bash
# GET
curl http://localhost:8080/
# With query params
curl "http://localhost:8080/search?q=test"
# POST form
curl -X POST http://localhost:8080/contact -d "name=Alice&email=alice@example.com"
# JSON API
curl http://localhost:8080/api/users -H "Content-Type: application/json" -d '{"name":"Alice"}'
# With cookies
curl -c cookies.txt http://localhost:8080/login -d "user=admin&pass=secret"
curl -b cookies.txt http://localhost:8080/dashboard
Common Pitfalls
- •Parts need routes - Add to basil.yaml
routes: - •Parts export functions only - No variables in .part files
- •Session secret required - Set in basil.yaml for persistent sessions
- •Database path relative to config - Not to handler file
- •File writes need whitelist - Use
security.allow_writein config
Quick Reference
Commands
bash
./basil --init myapp # Create project ./basil --dev # Run dev server ./basil --dev --port 3000 # Custom port
Config Essentials
yaml
server: port: 8080 site: ./site # Filesystem routing sqlite: ./data.db # Database session: secret: "32-char-secret" auth: enabled: true protected_paths: ["/admin"]
Import Essentials
parsley
let {method, request, response} = import @basil/http
let {session, user} = import @basil/auth
let {redirect, notFound} = import @std/api
Detailed Documentation
For comprehensive guides, see:
- •references/CONFIGURATION.md - Complete basil.yaml reference
- •references/PARTS.md - Interactive components guide
- •references/DATABASE.md - Database operations
- •references/TESTING.md - Testing strategies
- •Basil API:
docs/basil/reference.md- Full language reference with all operators and builtins - •Parsley:
docs/parsley/CHEATSHEET.mdanddocs/parsley/reference.md
Best Practices
- •Use
./basil --devfor auto-reload and detailed errors - •Test with curl before browser testing
- •Check
http://localhost:8080/__dev/logfor debugging - •Validate Parsley with
./parsbefore using in handlers - •Use
{data, error}pattern for error handling - •Set session secret for persistent sessions
- •Whitelist directories for file operations