AgentSkillsCN

basil-development

利用 HTTP 处理程序、路由、会话、认证、数据库,以及交互式组件(Parts)开发并测试 Basil Web 应用程序。

SKILL.md
--- frontmatter
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

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

  1. Parts need routes - Add to basil.yaml routes:
  2. Parts export functions only - No variables in .part files
  3. Session secret required - Set in basil.yaml for persistent sessions
  4. Database path relative to config - Not to handler file
  5. File writes need whitelist - Use security.allow_write in 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:

Best Practices

  1. Use ./basil --dev for auto-reload and detailed errors
  2. Test with curl before browser testing
  3. Check http://localhost:8080/__dev/log for debugging
  4. Validate Parsley with ./pars before using in handlers
  5. Use {data, error} pattern for error handling
  6. Set session secret for persistent sessions
  7. Whitelist directories for file operations