| name | basil-development |
| description | Develop and test Basil web applications with HTTP handlers, routing, sessions, authentication, databases, and interactive components (Parts). |
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
./basil --init myapp && cd myapp
./basil --dev
curl http://localhost:8080/
Core Concepts
1. Handlers
Parsley files that handle HTTP requests.
// site/index.pars
<html>
<body>
<h1>"Welcome!"</h1>
<p>`Search: {@params.q ?? 'none'}`</p>
</body>
</html>
2. Configuration (basil.yaml)
server:
port: 8080
site:
path: ./site
database:
path: ./data.db
session:
secret: "your-32-char-secret"
3. Parts (Interactive Components)
.part files with functions that return HTML. Update without page reload.
// 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:
<Part src={@~/parts/counter.part} count={0}/>
Important: Parts need routes in basil.yaml:
routes:
- path: /parts/counter.part
handler: ./parts/counter.part
Essential Imports
HTTP Context (@basil/http)
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)
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
@params // URL/form parameters
@DB // Server database (configured in basil.yaml)
@now // Current datetime
@env.HOME // Environment variables
Database Operations
// 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
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
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
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
curl http://localhost:8080/
curl "http://localhost:8080/search?q=test"
curl -X POST http://localhost:8080/contact -d "name=Alice&email=alice@example.com"
curl http://localhost:8080/api/users -H "Content-Type: application/json" -d '{"name":"Alice"}'
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_write in config
Quick Reference
Commands
./basil --init myapp
./basil --dev
./basil --dev --port 3000
Config Essentials
server:
port: 8080
site:
path: ./site
database:
path: ./data.db
session:
secret: "32-char-secret"
auth:
enabled: true
protected_paths: ["/admin"]
Import Essentials
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:
Best Practices
- Use
./basil --dev for auto-reload and detailed errors
- Test with curl before browser testing
- Check
http://localhost:8080/__dev/log for debugging
- Validate Parsley with
./pars before using in handlers
- Use
{data, error} pattern for error handling
- Set session secret for persistent sessions
- Whitelist directories for file operations