Skip to main content

What’s a Website in Ensemble?

In Ensemble, websites are built from ensembles with HTTP triggers:
  • Each route = one ensemble file
  • Pages render HTML using templates
  • APIs return JSON data
  • Static files (robots.txt, sitemap.xml) are ensembles too
No framework boilerplate. Just YAML configuration + HTML templates.

Quick Example: Hello World Page

Create ensembles/pages/hello.yaml:
Run: ensemble conductor start Visit: http://localhost:8787/hello That’s it! You just created a web page.

Website Structure

Organize your site by feature:
Key principle: One file per route. Each file is an ensemble with trigger: {type: http}.

Example 1: Homepage with Dynamic Content

Let’s fetch blog posts from a database and display them. Create ensembles/pages/home.yaml:
Visit: http://localhost:8787/

Example 2: Dynamic Blog Post Page

Create ensembles/pages/blog-post.yaml:
Visit: http://localhost:8787/blog/my-first-post ★ Insight ───────────────────────────────────── The :slug in the path becomes available as ${input.params.slug}. This is how you build dynamic routes with path parameters. ─────────────────────────────────────────────────

Example 3: Contact Form (GET + POST)

Handle both displaying a form (GET) and processing submissions (POST) in one ensemble. Create ensembles/pages/contact.yaml:
★ Insight ───────────────────────────────────── One ensemble handles both GET (show form) and POST (submit). Use ${metadata.method} to check which HTTP method was used. Add rate limiting to prevent spam! ─────────────────────────────────────────────────

Example 4: JSON API Endpoint

Not everything needs to be HTML. Create ensembles/api/users.yaml:
Test:
Returns:

Example 5: Static Files (robots.txt, sitemap.xml)

Even “static” files are ensembles - but they can be dynamic!

robots.txt

Create ensembles/static/robots.yaml:

sitemap.xml (Dynamic from Database)

Create ensembles/static/sitemap.yaml:
★ Insight ───────────────────────────────────── Your sitemap is generated dynamically from the database! As you add blog posts, they automatically appear in the sitemap. Add caching to avoid querying the database on every request. ─────────────────────────────────────────────────

Template Engines

Ensemble supports three template engines:

2. Handlebars

3. Simple (String Interpolation)

Authentication

Protect routes with authentication:
Or make routes public:
Default behavior: Routes require auth unless you set public: true.

CORS for APIs

Enable cross-origin requests:

Best Practices

1. Organize by Feature

✅ Good:
❌ Bad:

2. Use Path Parameters

✅ Good:
❌ Bad:

3. Add Rate Limiting to Forms

4. Cache Expensive Operations

5. Validate Input

Testing Your Website

Create tests/pages.test.ts:
Run: pnpm test

Deployment

Deploy to Cloudflare Workers:
Your website is now live on Cloudflare’s global edge network!

What You Built

In this guide, you created:
  • ✅ Homepage with dynamic database content
  • ✅ Blog post pages with URL parameters
  • ✅ Contact form with validation and email
  • ✅ JSON API with authentication
  • ✅ Dynamic sitemap.xml from database
  • ✅ Static robots.txt file
All using simple YAML configuration and HTML templates. No framework boilerplate!

Next Steps

Triggers

Deep dive into triggers

HTML Operation

Advanced HTML rendering

Data Operation

Database operations

Email Operation

Send emails

Troubleshooting

Problem: Visiting /hello returns 404Fixes:
  1. Rebuild: pnpm run build (ensembles are discovered at build time)
  2. Check path in ensemble matches URL: path: /hello
  3. Ensure ensemble is in ensembles/ directory
Problem: Page shows {{ post.title }} literallyFixes:
  1. Set template engine: templateEngine: liquid
  2. Check data is passed: data: { post: ${fetch-post} }
  3. Verify variable names match template
Problem: POST request fails or does nothingFixes:
  1. Add POST to methods: methods: [GET, POST]
  2. Check condition uses correct metadata: ${metadata.method === 'POST'}
  3. Verify form action matches path: <form method="POST" action="/contact">
Problem: ${fetch-posts} is empty arrayFixes:
  1. Check binding matches wrangler.toml: binding: DB
  2. Verify database has data: wrangler d1 execute DB --command "SELECT * FROM posts"
  3. Run migrations: wrangler d1 migrations apply DB