Scaffold a Project
The fastest way to start is thechirp newcommand:
chirp new myapp
cd myapp
python app.py
Open http://127.0.0.1:8000 in your browser. This generates an app.py, a templates/ directory with base.html and index.html, a static/ directory with style.css, and a tests/directory with a smoke test.
For an even smaller starting point:
chirp new myapp --minimal
This generates only app.py and templates/index.html.
Hello World (Manual)
You can also create a project by hand. Create a file calledapp.py:
from chirp import App
app = App()
@app.route("/")
def index():
return "Hello, World!"
app.run()
Run it:
python app.py
Open http://127.0.0.1:8000in your browser. Five lines to hello world.
Add Templates
Create atemplates/ directory and add base.html:
<!DOCTYPE html>
<html>
<head><title>{{ title }}</title></head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Add templates/index.html:
{% extends "base.html" %}
{% block content %}
<h1>{{ title }}</h1>
<p>Welcome to my Chirp app.</p>
{% endblock %}
Update app.py:
from chirp import App, Template
app = App()
@app.route("/")
def index():
return Template("index.html", title="Home")
app.run()
Route functions return values. Template tells the framework to render index.htmlwith the given context via kida.
Fragment Rendering
This is where Chirp diverges from Flask. Add a search feature that works as both a full page and an htmx fragment.
Addtemplates/search.html:
{% extends "base.html" %}
{% block content %}
<h1>Search</h1>
<input type="search" name="q"
hx-get="/search" hx-target="#results" hx-trigger="input changed delay:300ms">
{% block results %}
<div id="results">
{% for item in results %}
<p>{{ item }}</p>
{% endfor %}
</div>
{% endblock %}
{% endblock %}
Update app.py:
from chirp import App, Template, Fragment, Request
app = App()
ITEMS = ["apple", "banana", "cherry", "date", "elderberry"]
@app.route("/")
def index():
return Template("index.html", title="Home")
@app.route("/search")
def search(request: Request):
q = request.query.get("q", "")
results = [i for i in ITEMS if q.lower() in i.lower()] if q else ITEMS
if request.is_fragment:
return Fragment("search.html", "results", results=results)
return Template("search.html", title="Search", results=results)
app.run()
Full page navigation renders everything. An htmx request renders just the resultsblock. Same template, same data, different scope.
Add htmx
To make the fragment rendering work, include htmx in yourbase.html:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
Now the search input sends requests to /search via htmx, and Chirp responds with just the resultsblock -- no full page reload, no separate partials directory, no JavaScript.
Next Steps
- Return Values -- All the types you can return
- Fragments -- Deep dive into fragment rendering
- Streaming HTML -- Progressive page rendering
- Server-Sent Events -- Real-time updates