--- title: Quick Start (Deno) description: Get started with Sapling using Deno publishedAt: "2024-01-01" --- # Quick Start with Deno This guide will help you create your first Sapling website using Deno. ## Prerequisites - [Deno](https://deno.com) installed on your machine - A code editor (we recommend VS Code with the Deno extension) ## Create a New Project ```bash deno -A jsr:@sapling/create ``` Select the "Basics" template. ## Start the Development Server ```bash deno task dev ``` Your site is now running at [http://localhost:8080](http://localhost:8080)! ## Project Structure ``` my-website/ ├── components/ # Reusable UI components ├── layouts/ # Page layouts ├── pages/ # Your pages/routes ├── static/ # Static assets ├── deno.json # Project configuration └── index.ts # Entry point ``` ## Create Your First Page 1. Create a new file `pages/about.ts`: ```typescript import Layout from "../layouts/Layout.ts"; import { html } from "@sapling/sapling"; export default async function About() { return await Layout({ children: html`

About Us

Welcome to our website!

Go back home
`, }); } ``` 2. Add the route in `index.ts` underneath the `Home` route: ```typescript site.get("/about", async (c) => c.html(await About())); ``` Visit [http://localhost:8080/about](http://localhost:8080/about) to see your new page! ## Next Steps - Learn about [Routing](/docs/routing) - Explore [Layouts](/docs/layouts) - Check out the [Examples](https://github.com/withsapling/examples) --- title: Routing description: Learn how routing works in Sapling publishedAt: "2024-01-01" --- # Routing Sapling uses a simple and powerful routing system based on URL pattern matching. Routes are defined explicitly using the Sapling instance methods, providing a clean and type-safe way to handle HTTP requests. ## Route Handlers Routes are defined using the Sapling instance methods for different HTTP methods: ```typescript import { Sapling } from '@sapling/sapling'; const site = new Sapling(); // Basic route site.get("/", (c) => { return c.html("

Welcome to Sapling

"); }); // Dynamic route with parameters site.get("/users/:id", (c) => { const userId = c.req.param("id"); return c.json({ userId }); }); ``` Each route handler receives a context object (`c`) and can return one of the following types: - `Response` - `Promise` - `Promise` - `null` ## URL Pattern Matching Sapling uses the URLPattern API for route matching. Each route is automatically registered with and without a trailing slash, so both `/users` and `/users/` will match the same handler. ### Route Parameters Dynamic segments in routes are defined using the `:parameter` syntax and can be accessed via `c.req.param()`: ```typescript site.get("/posts/:slug/comments/:commentId", (c) => { const slug = c.req.param("slug"); const commentId = c.req.param("commentId"); return c.json({ slug, commentId }); }); ``` ## HTTP Methods Sapling supports all standard HTTP methods: ```typescript // GET request site.get("/api/data", (c) => { return c.json({ data: "here" }); }); // POST request site.post("/api/users", async (c) => { const data = await c.req.json(); return c.json({ created: true }); }); // PUT request site.put("/api/users/:id", async (c) => { const id = c.req.param("id"); const data = await c.req.json(); return c.json({ updated: id }); }); // DELETE request site.delete("/api/users/:id", (c) => { const id = c.req.param("id"); return c.json({ deleted: id }); }); // PATCH request site.patch("/api/users/:id", async (c) => { const id = c.req.param("id"); const data = await c.req.json(); return c.json({ patched: id }); }); ``` ## Response Types Sapling provides several convenient methods for returning different types of responses: ```typescript // HTML Response site.get("/page", (c) => { return c.html("

Welcome

"); }); // JSON Response site.get("/api/data", (c) => { return c.json({ hello: "world" }); }); // Text Response site.get("/text", (c) => { return c.text("Plain text response"); }); // Redirect Response site.get("/old-path", (c) => { return c.redirect("/new-path"); }); // Custom Response site.get("/custom", (c) => { return new Response("Custom response", { status: 201, headers: { "Content-Type": "text/plain", "X-Custom-Header": "value" } }); }); ``` ## 404 Handling Sapling provides a built-in 404 handler that returns "Not found" with a 404 status code. You can customize this behavior using `setNotFoundHandler`: ```typescript site.setNotFoundHandler((c) => { return c.html(`

Page Not Found

The requested page could not be found.

`); }); ``` The not found handler receives the same context object as regular routes and can return any valid response type. ## Request Context Each route handler receives a context object with the following properties and methods: ```typescript interface Context { req: { param: (name: string) => string; method: string; url: string; headers: Headers; }; state: Record; query: () => URLSearchParams; jsonData: () => Promise; formData: () => Promise; html: (content: string) => Response; json: (data: unknown) => Response; text: (content: string) => Response; redirect: (location: string, status?: number) => Response; } ``` ## Best Practices 1. **Route Organization**: Define specific routes before more general ones to ensure proper matching. 2. **Type Safety**: Use TypeScript interfaces for request and response data: ```typescript interface User { id: string; name: string; } site.post("/api/users", async (c) => { const data = await c.jsonData(); return c.json(data); }); ``` 3. **Error Handling**: Implement proper error handling in your routes: ```typescript site.get("/api/protected", async (c) => { try { const data = await fetchData(); return c.json(data); } catch (error) { return new Response(JSON.stringify({ error: "Server Error" }), { status: 500, headers: { "Content-Type": "application/json" } }); } }); ``` 4. **Request Validation**: Validate input data before processing: ```typescript site.post("/api/posts", async (c) => { const data = await c.jsonData<{ title: string; content: string }>(); if (!data.title || !data.content) { return new Response(JSON.stringify({ error: "Missing required fields" }), { status: 400, headers: { "Content-Type": "application/json" } }); } // Process valid data... }); ``` --- title: Quick Start (Node.js) description: Get started with Sapling using Node.js publishedAt: "2024-01-01" --- # Quick Start with Node.js This guide will help you create your first Sapling website using Node.js. ## Prerequisites - [Node.js](https://nodejs.org) (version 18 or higher) - npm (comes with Node.js) ## Create a New Project ```bash npm create sapling@latest ``` Select the "Basics" template. ## Start the Development Server ```bash npm run dev ``` Your site is now running at [http://localhost:8080](http://localhost:8080)! ## Project Structure ``` my-website/ ├── components/ # Reusable UI components ├── layouts/ # Page layouts ├── pages/ # Your pages/routes ├── static/ # Static assets ├── package.json # Project configuration └── index.ts # Entry point ``` ## Create Your First Page 1. Create a new file `pages/about.ts`: ```typescript import Layout from "../layouts/Layout.ts"; import { html } from "@sapling/sapling"; export default async function About() { return await Layout({ children: html`

About Us

Welcome to our website!

Go back home
`, }); } ``` 2. Add the route in `index.ts` underneath the `Home` route: ```typescript site.get("/about", async (c) => c.html(await About())); ``` Visit [http://localhost:8080/about](http://localhost:8080/about) to see your new page! ## Next Steps - Learn about [Routing](/docs/routing) - Explore [Layouts](/docs/layouts) - Check out the [Examples](https://github.com/saplingjs/examples) --- title: FAQ draft: false publishedAt: "2024-01-01" --- # Frequently Asked Questions ## What is Sapling? Sapling is a lightweight web framework for building server-rendered websites with TypeScript. It's designed to be simple, fast, and easy to learn while providing modern development features. ## Why should I choose Sapling over other frameworks? Sapling offers: - Zero client-side JavaScript by default - Simple, intuitive HTML templating - First-class TypeScript support - Fast server-side rendering - Works with any JS runtime such as Deno, Node.js, Bun, etc. - No build step required - Code that embraces web standards rather than fights them ## Can I still use React or Svelte or other frontend frameworks? Yes, because Sapling is more of a bare bones server side framework that generates HTML you can use any frontend framework you prefer in specific parts of your website/application. We might be biased but we think most sites are better off with plain old HTML, CSS, and JS with a sprinkling of framework specific components. ## Do I need to know TypeScript to use Sapling? While Sapling is built with TypeScript, you can write your code in JavaScript if you prefer. However, using TypeScript provides better developer experience with type checking and IDE support. ## Do I need to use Deno to use Sapling? No, Sapling works with all major JavaScript runtime environments including Deno, Node.js, Bun, and CloudFlare Workers. You'll find that we use Deno often in documentation and examples because it's our preferred runtime with built-in TypeScript support which allows us to skip the configuration and build step. However, that doesn't mean Sapling is limited to Deno. ## Do I have to use Sapling for routing? No, you can use any routing solution you prefer. We aim to be compatible with the most popular server routers out there such as [Hono](https://hono.dev), [Express](https://expressjs.com), [Koa](https://koajs.com), [Oak](https://deno.land/x/oak@v17.1.3) [Fastify](https://fastify.io), etc. Sapling's routing is designed to be simple, easy to use, and adds features specific to websites, but it may not meet the needs of all applications. In those cases simply use our Layout component inside of your pages to return HTML. ## Why did you build Sapling when Hono already exists? Hono is a great framework and we love it. However, it became pretty clear that at some point we would need our own router that adds specific features and optimizations for websites and applications. Hono is a great router for APIs and we think it's great for that use case. However, we think/hope you'll see over time that Sapling is a better fit for building websites and applications. ## So when should I use Sapling and when should I use Hono? It depends. The best answer we have is if you're primarily building an API then use Hono. If you're primarily building a website or application then use Sapling. We aim to be closely compatible with Hono's context format so you can easily migrate between the two as needed. ## How does routing work in Sapling? Sapling uses a simple and powerful routing system based on URL pattern matching. Routes are defined explicitly using Sapling instance methods for different HTTP methods. For example: ```typescript site.get("/", (c) => c.html("

Welcome

")); site.get("/users/:id", (c) => c.json({ id: c.req.param("id") })); ``` Each route handler receives a context object and can return various response types including HTML, JSON, or custom responses. For more details, see the [routing documentation](/docs/routing). ## Where can I deploy Sapling applications? Pretty much anywhere! Sapling applications can be deployed to any platform that supports Deno or Node.js, including: - Deno Deploy - Google Cloud Run - Vercel Edge Functions - Netlify Edge Functions - AWS Lambda - Cloudflare Workers - Render - Your own server running Deno, Node.js, or Bun ## How do I handle forms and user input? Sapling provides built-in utilities for handling form submissions and user input through its server-side functions. You can access form data through the request object in your page handlers. ## Is Sapling production-ready? Yes, Sapling is being used in production by various projects. However, as with any technology choice, make sure to evaluate it against your specific requirements. We are still not at version 1.0 and are actively working on improving the framework. ## Where can I get help? - Check the documentation - Open an issue on GitHub - Follow the examples in our [repository](https://github.com/withsapling/examples) ## How can I contribute to Sapling? We welcome contributions! You can: - Submit bug reports and feature requests on GitHub - Contribute to the documentation - Submit pull requests - Share your Sapling projects with the community --- title: LLMs Reference File description: Learn about Sapling's LLMs reference file for AI assistants publishedAt: "2024-01-01" --- # LLMs Reference File Sapling provides a comprehensive reference file for AI language models at https://sapling.land/docs/llms.txt. This file contains the entire documentation in a single, easily accessible format that AI assistants can use to better understand and work with Sapling. ## Purpose The LLMs reference file serves several purposes: - Provides AI assistants with complete documentation in one file - Enables better context and understanding of Sapling's features - Helps AI tools provide more accurate assistance - Ensures consistent information across different AI platforms ## Accessing the File The LLMs reference file is available at: ```txt https://sapling.land/docs/llms.txt ``` Or download it below: Download llms.txt ## Contents The file includes all documentation sections: - Getting Started guides - Core Concepts - API References - Best Practices - Examples - FAQs ## Generation The LLMs reference file is automatically generated from the documentation markdown files. This ensures it stays up-to-date with the latest documentation changes. ## Usage When working with AI assistants or tools: 1. Point it to the LLMs file url below or upload it to your project for comprehensive documentation ```txt https://sapling.land/docs/llms.txt ``` 2. Use it as a reference for accurate information about Sapling 3. Include it in your project's AI configuration files ## Example You could add this to Cursor as a doc reference to use whenever you need to reference Sapling's documentation. --- title: Sapling Islands description: Learn how to use the sapling-island web component for progressive hydration publishedAt: "2024-01-01" --- # Sapling Islands The `sapling-island` web component is a lightweight solution for implementing islands architecture in your Sapling applications. It allows you to progressively hydrate parts of your page, loading JavaScript and styles only when needed. ## Installation Add the Sapling Islands script to your page via CDN: ```html ``` Add the following CSS to ensure proper rendering of the content inside the islands: ```css sapling-island { display: contents; } ``` There may be some cases where you don't want `display: contents`; however, in most cases it's a good idea to use it. ## Overview Islands architecture is a pattern where most of your page remains static HTML, with interactive "islands" that get hydrated with JavaScript when needed. This approach can significantly improve initial page load performance by deferring the loading of non-critical JavaScript. ## Basic Usage Wrap any content that requires JavaScript or additional styles in a `sapling-island` component: ```html
Your interactive content here
``` ## Loading Strategies The component supports several loading strategies through the `loading` attribute: ### Load Immediately Loads immediately when the page loads: ```html ``` ### Visible Loads when the component becomes visible in the viewport: ```html ``` ### Idle Loads when the browser is idle: ```html ``` ### Media Query Loads when a media query condition is met: ```html ``` ## Timeout Option You can specify a timeout for loading strategies using the `timeout` attribute: ```html ``` ## Events The component dispatches a `island:hydrated` event when hydration is complete: ```javascript document.querySelector('sapling-island').addEventListener('island:hydrated', () => { console.log('Island has been hydrated'); }); ``` ## Best Practices 1. **Template Content Only** Only place `
...
``` 2. **Progressive Enhancement** Design your islands to enhance existing static content rather than being required for basic functionality: ```html
Static content
Interactive features
``` 3. **Performance Considerations** - Use `onvisible` for below-the-fold content - Use `idle` for non-critical enhancements - Use media queries for device-specific features (mobile navigation, desktop features, etc.) - Set appropriate timeouts for critical features ## Example: Interactive Comments Section Here's a complete example of using `sapling-island` for a comments section: ```html

Comments

``` ## Browser Support The `sapling-island` component uses standard web APIs and includes fallbacks for broader browser support: - Uses `IntersectionObserver` for visibility detection - Falls back to `setTimeout` when `requestIdleCallback` is not available - Supports all modern browsers that implement Custom Elements v1 For more examples and use cases, check out our [Examples](/docs/examples) section. --- title: Acknowledgements description: Special thanks to the projects and people that make Sapling possible publishedAt: "2024-01-01" --- # Acknowledgements Sapling stands on the shoulders of giants. We'd like to acknowledge and thank the following projects and communities that make Sapling possible: ## Core Technologies - [Hono](https://github.com/honojs/hono) - We use Hono's amazing HTML helpers (`html` and `raw`) for escaping HTML templates. We're also huge fans of their routing approach which is why we've borrowed some of their ideas for Sapling. - [UnoCSS](https://github.com/unocss/unocss) - We use UnoCSS under the hood for atomic CSS. - [marked](https://github.com/markedjs/marked) - Our markdown parser. - [shiki](https://github.com/shikijs/shiki) - The code highlighter for syntax highlighting. ## Inspiration Here are some projects that have inspired us: - [Astro](https://astro.build) - Astro's approach to building modern websites with zero JavaScript by default was a core concept that we wanted to be central to Sapling. - [Hono](https://hono.dev) - Hono's routing and middleware system was a big inspiration for Sapling. - [Fresh](https://fresh.deno.dev) - Fresh's SSR-first, no build by default approach was a huge reason why Sapling is structured the way it is. - [11ty](https://www.11ty.dev) - 11ty's approach to not tie the framework to the bundler was a big inspiration for Sapling. - [is-land](https://github.com/11ty/is-land) - Our `sapling-island` component is heavily inspired by 11ty's `is-land` web component and Astro's `islands` architecture through a similar approach. Each of these projects are amazing in their own right and we're so thankful they exist as fully open source projects that we can all learn from. ## Runtime Support - **[Deno](https://deno.land)**: The modern and secure JavaScript runtime - **[Node.js](https://nodejs.org)**: The foundational JavaScript runtime - **[Bun](https://bun.sh)**: The all-in-one JavaScript runtime ## Community We're grateful to all the developers and users who have contributed to Sapling through: - Bug reports and fixes - Feature suggestions - Documentation improvements - Community support ## License Sapling is open source software licensed under the MIT license. We believe in the power of open source and are committed to keeping Sapling free and open for everyone. --- title: HTML Templating description: Learn how to use Sapling's HTML templating methods publishedAt: "2024-01-01" --- # HTML Templating Sapling provides two main methods for rendering HTML content: `html` and `raw`. These are convenient wrappers around the `html` and `raw` methods from [Hono](https://hono.dev). ## The html Template Literal The `html` template literal allows you to write HTML with dynamic content interpolation: ```typescript import { html } from "@sapling/sapling"; function Greeting({ name }: { name: string }) { return html`

Hello, ${name}!

`; } ``` ### Safety Features The `html` template automatically escapes interpolated values to prevent XSS attacks: ```typescript const userInput = ''; html`
${userInput}
`; // Safely escaped ``` ### Nested Components You can nest components and interpolate their results: ```typescript function Header() { return html`
Site Header
`; } function Page() { return html`
${Header()}
Content
`; } ``` ## The raw Method The `raw` method is used when you need to insert pre-rendered HTML content without escaping: ```typescript import { html, raw } from "@sapling/sapling"; function Article({ content }: { content: string }) { return html`
${raw(content)}
`; } ``` ### Use Cases for raw - Rendering markdown content - Inserting sanitized HTML from a CMS - Including pre-rendered component output ```typescript import { renderMarkdown } from "@sapling/markdown"; async function MarkdownContent({ markdown }: { markdown: string }) { const rendered = await renderMarkdown(markdown); return html`
${raw(rendered)}
`; } ``` ## Conditional Rendering You can use standard JavaScript expressions within templates: ```typescript function ConditionalContent({ isLoggedIn }: { isLoggedIn: boolean }) { return html`
${isLoggedIn ? html`` : html`` }
`; } ``` ## List Rendering Render arrays of content using map: ```typescript function ItemList({ items }: { items: string[] }) { return html`
    ${items.map(item => html`
  • ${item}
  • `)}
`; } ``` ## Best Practices 1. **Use html by Default**: Always use the `html` template literal unless you specifically need `raw` 2. **Sanitize Raw Content**: When using `raw`, ensure the content is from a trusted source or properly sanitized 3. **Type Safety**: Leverage TypeScript for component props 4. **Keep It Simple**: Prefer small, focused components over complex templates 5. **Performance**: Avoid unnecessary nesting of templates ## Security Considerations - The `html` template literal automatically escapes content to prevent XSS - Only use `raw` with trusted content - Always sanitize user-generated content before rendering - Be cautious when rendering HTML from external sources --- title: Why Sapling? description: Learn why Sapling is the ideal choice for modern web development publishedAt: "2024-01-01" --- # Why Sapling? Sapling was created with a simple goal: to make building modern server-side rendered (SSR) websites as straightforward as possible. While web development has grown increasingly complex, we believe that creating a website shouldn't require learning complex build tools or managing heavy client-side JavaScript. ## Core Principles ### 1. Zero JavaScript by Default Sapling takes a HTML-first approach. Your pages are server-rendered and work without JavaScript enabled. This means: - Better performance - Improved accessibility - Enhanced SEO - Lower bandwidth usage Add JavaScript only when you need it, not because your framework requires it. ### 2. No Build Step Required Unlike many modern frameworks, Sapling doesn't require a build step in development: - Start coding immediately - No bundler configuration - Native TypeScript support ### 3. Multi-Runtime Support Sapling is designed to work across different JavaScript runtimes: - **Deno**: For modern, secure applications - **Node.js**: For traditional Node.js environments - **Bun**: For maximum performance Choose the runtime that best fits your needs without changing your code. ### 4. Built-in Tailwind Sapling uses [UnoCSS](https://github.com/unocss/unocss) for CSS, which means you can use just about any Tailwind classes and they will just work. We even include the Tailwind reset styles by default so it feels like you're using Tailwind out of the box. ### 5. Simple but Powerful Sapling provides powerful features without complexity: - File-based routing - Type-safe templating - Built-in UnoCSS support - Markdown processing - Layout system ## When to Use Sapling Sapling is ideal for: - **Content-focused websites**: Blogs, documentation, marketing sites - **Server-rendered applications**: Where SEO and initial load performance are crucial - **Multi-page applications**: Traditional website structures with multiple pages - **API-driven websites**: You want to serve a few webpages from your API instead of setting up an entirely new frontend project ## When to Consider Alternatives Sapling might not be the best choice for: - Single-page applications (SPAs) with complex client-side state - Applications requiring real-time updates - Projects that need extensive client-side JavaScript ## Performance Benefits ### Server-Side Rendering - Faster Time to First Byte (TTFB) - Better Core Web Vitals - Reduced client-side processing ### Minimal JavaScript - Smaller bundle sizes - Less parsing and execution time - Reduced memory usage ### Efficient Asset Handling - Built-in static file serving - Automatic HTTP caching headers - Optimized asset loading ## Developer Experience Sapling prioritizes developer experience: ```typescript // Simple component creation function Greeting() { return html`

Hello, World!

`; } // Easy routing site.get("/", async (c) => c.html(await Home())); // Straightforward layouts export default async function Layout(props: LayoutProps) { return await SaplingLayout({ title: "My Site", children: props.children }); } ``` ## Modern Features Despite its simplicity, Sapling includes modern features: - **TypeScript First**: Full type safety out of the box - **UnoCSS Integration**: Modern atomic CSS - **Dark Mode Support**: Built-in dark mode utilities - **API Routes**: Easy backend functionality - **Markdown Support**: A first party markdown to HTML implementation with syntax highlighting ## Inspiration See our [acknowledgements](/docs/acknowledgements) to learn more about the projects that inspired Sapling. ## Community and Ecosystem Sapling benefits from: - Regular updates and improvements - Growing ecosystem of plugins and tools - Comprehensive documentation ## Getting Started Ready to try Sapling? Check out our Quick Start guides: - [Quick Start (Deno)](/docs/quick-start-deno) - [Quick Start (Node.js)](/docs/quick-start-node) --- title: API Routes description: Learn how to create and use API routes in Sapling publishedAt: "2024-01-01" --- # API Routes API routes allow you to create server-side endpoints that can handle HTTP requests and return JSON or other data formats. Sapling makes it easy to create API routes alongside your regular page routes. ## Basic API Route Create an API route by using the `site.get()`, `site.post()`, or other HTTP method handlers: ```typescript // routes/api/hello.ts site.get("/api/hello", (c) => { return c.json({ message: "Hello from Sapling API!" }); }); ``` ## HTTP Methods Sapling supports all standard HTTP methods: ```typescript // GET request site.get("/api/users", (c) => { return c.json({ users: ["Alice", "Bob"] }); }); // POST request site.post("/api/users", async (c) => { const body = await c.req.json(); // Handle user creation return c.json({ success: true }); }); // PUT request site.put("/api/users/:id", async (c) => { const id = c.req.param("id"); const body = await c.req.json(); // Handle user update return c.json({ success: true }); }); // DELETE request site.delete("/api/users/:id", (c) => { const id = c.req.param("id"); // Handle user deletion return c.json({ success: true }); }); // PATCH request site.patch("/api/users/:id", async (c) => { const id = c.req.param("id"); const body = await c.req.json(); // Handle partial user update return c.json({ success: true }); }); ``` ## Request Handling ### Query Parameters Access query parameters using `c.query()`: ```typescript site.get("/api/search", (c) => { const query = c.query().get("q"); const page = parseInt(c.query().get("page") || "1"); return c.json({ query, page, results: [] }); }); ``` ### Route Parameters Access route parameters using `c.req.param()`: ```typescript site.get("/api/users/:id/posts/:postId", (c) => { const userId = c.req.param("id"); const postId = c.req.param("postId"); return c.json({ userId, postId }); }); ``` ### Request Body Parse JSON request bodies using `c.jsonData()`: ```typescript site.post("/api/posts", async (c) => { const body = await c.jsonData<{ title: string; content: string }>(); // Validate the request body if (!body.title || !body.content) { return new Response(JSON.stringify({ error: "Missing required fields" }), { status: 400, headers: { "Content-Type": "application/json" } }); } // Process the post return c.json({ id: "new-post-id", ...body }); }); ``` ### Form Data Handle form data using `c.formData()`: ```typescript site.post("/api/upload", async (c) => { const form = await c.req.formData(); const file = form.get("file"); // Process the file return c.json({ success: true }); }); ``` ## Response Types ### JSON Responses Return JSON responses using `c.json()`: ```typescript site.get("/api/data", (c) => { return c.json({ success: true, data: { message: "Hello World" } }); }); ``` ### HTML Responses Return HTML responses using `c.html()`: ```typescript site.get("/page", (c) => { return c.html("

Welcome to my page

"); }); ``` ### Text Responses Return plain text responses using `c.text()`: ```typescript site.get("/plain", (c) => { return c.text("Hello World"); }); ``` ### Custom Responses For custom status codes or headers, use the Response object directly: ```typescript site.post("/api/items", async (c) => { // Created successfully return new Response(JSON.stringify({ id: "new-item" }), { status: 201, headers: { "Content-Type": "application/json" } }); }); site.get("/api/protected", (c) => { // Unauthorized return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } }); }); ``` ### Redirects Perform redirects using `c.redirect()`: ```typescript site.get("/old-page", (c) => { return c.redirect("/new-page"); }); site.get("/permanent-redirect", (c) => { return c.redirect("/new-location", 301); }); ``` ## Error Handling Implement error handling for your API routes: ```typescript site.get("/api/protected-resource", async (c) => { try { // Check authentication const user = await authenticateUser(c); if (!user) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } }); } // Process request const data = await fetchProtectedData(); return c.json(data); } catch (error) { console.error("API Error:", error); return new Response(JSON.stringify({ error: "Internal Server Error" }), { status: 500, headers: { "Content-Type": "application/json" } }); } }); ``` ## TypeScript Support Use TypeScript interfaces for type-safe API handling: ```typescript interface User { id: string; name: string; email: string; } interface CreateUserBody { name: string; email: string; } site.post("/api/users", async (c) => { const body = await c.jsonData(); const user: User = { id: crypto.randomUUID(), name: body.name, email: body.email }; return c.json(user); }); ``` ## Best Practices 1. **Versioning**: Consider versioning your API routes: ```typescript site.get("/api/v1/users", (c) => { // V1 implementation }); site.get("/api/v2/users", (c) => { // V2 implementation }); ``` 2. **Validation**: Validate request data before processing: ```typescript site.post("/api/comments", async (c) => { const body = await c.jsonData<{ content: string }>(); if (!body.content || body.content.length < 10) { return new Response(JSON.stringify({ error: "Comment must be at least 10 characters" }), { status: 400, headers: { "Content-Type": "application/json" } }); } // Process valid comment return c.json({ success: true }); }); ``` 3. **Custom 404 Handler**: Set up a custom 404 handler: ```typescript site.setNotFoundHandler((c) => { return new Response("Custom Not Found", { status: 404, headers: { "Content-Type": "text/plain" } }); }); ``` ## Testing API Routes Create tests for your API routes: ```typescript // tests/api.test.ts import { assertEquals } from "https://deno.land/std/testing/asserts.ts"; Deno.test("GET /api/hello returns correct message", async () => { const response = await fetch("http://localhost:3000/api/hello"); const data = await response.json(); assertEquals(response.status, 200); assertEquals(data.message, "Hello from Sapling API!"); }); ``` For more examples and use cases, check out our [Examples](/docs/examples) section. --- title: Examples description: Explore example projects built with Sapling publishedAt: "2024-01-01" --- # Examples Explore our collection of example projects to learn how to build different types of applications with Sapling. All examples are available in our [Examples GitHub Repository](https://github.com/withsapling/examples). ## Basic Examples - [Basics](https://github.com/withsapling/examples/tree/main/deno/basics) - A basic Sapling application - [Hello World](https://github.com/withsapling/examples/tree/main/deno/hello-sapling) - A minimal Sapling application - [Basic Landing Page](https://github.com/withsapling/examples/tree/main/deno/single-file-landing-page) - Simple landing page example ## Content-Driven Examples - [Blog](https://github.com/withsapling/examples/tree/main/deno/blog) - Full blog implementation - [Documentation Site](https://github.com/withsapling/examples/tree/main/deno/docs) - Documentation site example ## CMS Examples - [Sanity](https://github.com/withsapling/examples/tree/main/deno/with-sanity) - [Sanity](https://sanity.io) CMS example - [Contentful](https://github.com/withsapling/examples/tree/main/deno/with-contentful) - [Contentful](https://www.contentful.com) CMS example - [Zenblog](https://github.com/withsapling/examples/tree/main/deno/with-zenblog) - [Zenblog](https://zenblog.com) CMS example ... More coming soon (PayloadCMS, Notion, Hygraph, DatoCMS, Storyblok, Prismic, etc.) ## Framework Examples - [Alpine.js](https://github.com/withsapling/examples/tree/main/deno/with-alpinejs) - Using Sapling with Alpine.js - [HTMX](https://github.com/withsapling/examples/tree/main/deno/with-htmx) - Using Sapling with HTMX - [Web Components](https://github.com/withsapling/examples/tree/main/deno/with-web-components) - Using Web Components. We know this is a not a framework (technically) #usetheplatform - [Petite Vue](https://github.com/withsapling/examples/tree/main/deno/with-petite-vue) - Using Petite Vue ## Integration Examples - [Motion](https://github.com/withsapling/examples/tree/main/deno/with-motion) - Animation with Motion - [Vite + Sapling](https://github.com/withsapling/examples/tree/main/deno/vite-sapling) - Using Sapling with Vite - [Embedded React App](https://github.com/withsapling/examples/tree/main/deno/with-react-app) - Embedding a client-side React App - [Gemini Chat](https://github.com/withsapling/examples/tree/main/deno/with-ai-gemini) - Simple AI example to send a message to Gemini - [OpenAI Chat](https://github.com/withsapling/examples/tree/main/deno/with-ai-openai) - Simple AI example to send a message to ChatGPT - [Resend Emails](https://github.com/withsapling/examples/tree/main/deno/with-resend) - Sending emails from a form with Resend - [Iconify Icons](https://github.com/withsapling/examples/tree/main/deno/with-iconify) - Using Iconify Icons - [Satori Open Graph Images](https://github.com/withsapling/examples/tree/main/deno/satori-og-images) - Generating Open Graph Images Server-Side with Satori - [Deno KV](https://github.com/withsapling/examples/tree/main/deno/with-deno-kv) - Using Deno KV ## Misc Examples - [Sitemap](https://github.com/withsapling/examples/tree/main/deno/generate-sitemap) - Generating a Sitemap ## Runtime Examples - [Deno](https://github.com/withsapling/examples/tree/main/deno) - Deno examples - [Node](https://github.com/withsapling/examples/tree/main/node) - Node.js examples - [Cloudflare Workers](https://github.com/withsapling/examples/tree/main/cf-workers) - Cloudflare Workers examples - [Bun](https://github.com/withsapling/examples/tree/main/bun) - Bun examples ## Deployment Examples - [Deno Deploy](https://github.com/withsapling/examples/tree/main/deployment/deno-deploy) - [Cloudflare Workers](https://github.com/withsapling/examples/tree/main/deployment/cf-workers/hello-sapling) - [Vercel Edge](https://github.com/withsapling/examples/tree/main/deployment/vercel-edge) - [Google Cloud Run Deno](https://github.com/withsapling/examples/tree/main/deployment/google-cloud-run-deno) - [Google Cloud Run Bun](https://github.com/withsapling/examples/tree/main/deployment/google-cloud-run-bun) - [Google Cloud Run Node](https://github.com/withsapling/examples/tree/main/deployment/google-cloud-run-node) ## Running Examples 1. Clone the repository: ```bash git clone https://github.com/withsapling/examples.git cd examples ``` 2. Navigate to an example: ```bash cd deno/hello-deno # or any other example ``` 3. Start the development server: ```bash deno task dev # for Deno examples # or npm run dev # for Node.js examples ``` Each example includes its own README with specific instructions and explanations. ``` --- title: Layouts description: Learn how to use layouts in Sapling to create consistent page structures publishedAt: "2024-01-01" --- # Layouts Layouts in Sapling provide a way to create consistent page structures and share common elements across multiple pages. ## Basic Layout A basic layout in Sapling is a TypeScript function that wraps your page content. Here's a simple example: ```typescript import { Layout as SaplingLayout, html, type LayoutProps } from "@sapling/sapling"; export type BaseLayoutProps = LayoutProps & { title?: string; description?: string; }; export default async function Layout(props: BaseLayoutProps) { return await SaplingLayout({ head: html` ${props.title} `, children: html`
${props.children}
` }); } ``` ## Layout Properties Layouts accept properties that extend Sapling's base `LayoutProps`: ```typescript type LayoutProps = { head?: unknown; // Additional head content bodyClass?: string; // Classes to apply to the body children?: unknown; // Page content unoConfig?: unknown; // UnoCSS configuration }; ``` ## Using Components in Layouts Layouts can import and use components to create consistent page structures: ```typescript import { Nav } from "../components/Nav.ts"; import { Footer } from "../components/Footer.ts"; export default async function Layout(props: BaseLayoutProps) { return await SaplingLayout({ // ... other props children: html`
${Nav()} ${props.children} ${Footer()}
` }); } ``` ## Styling with UnoCSS Sapling layouts can include UnoCSS configuration for consistent styling: ```typescript import { config } from "../uno.config.ts"; export default async function Layout(props: BaseLayoutProps) { return await SaplingLayout({ unoConfig: config, bodyClass: `font-sans @dark:bg-black @dark:text-white ${props.bodyClass ?? ''}`, // ... other props }); } ``` ## Nested Layouts You can create specialized layouts that extend your base layout: ```typescript import BaseLayout from "./Layout.ts"; export default async function DocsLayout(props: DocsLayoutProps) { return await BaseLayout({ ...props, children: html`
${props.children}
` }); } ``` ## Using Layouts in Pages To use a layout in a page component: ```typescript import Layout from "../layouts/Layout.ts"; export default async function HomePage() { return await Layout({ title: "Home - My Site", description: "Welcome to my website", children: html`

Welcome!

` }); } ``` ## Best Practices 1. **Type Safety**: Define proper TypeScript interfaces for your layout props 2. **Modularity**: Break down layouts into reusable components 3. **Flexibility**: Make layouts adaptable with optional props 4. **Dark Mode**: Include dark mode support using the `@dark:` prefix 5. **Performance**: Keep layouts lightweight and avoid unnecessary nesting --- title: Cursor Rules description: A custom .cursorrules.txt file for Cursor's AI publishedAt: "2025-01-10" --- ## Cursor Rules Below is a custom .cursorrules file you can add to your Sapling project. Place this text in a file named `.cursorrules` in the root of your project and it will be used by [Cursor](https://cursor.com) whenever you are using AI. ```txt You are an expert AI assistant and exceptional senior software developer, specializing in Deno and TypeScript with deep knowledge of the Sapling framework, and best practices. Sapling is a framework for building web applications. It is a server-side framework that allows you to build web applications in a way that is similar to how you would build a static site. Here are some of the best practices for using Sapling: 1. HTML Templating: - Use html\` \` template literals for safe HTML rendering - Use raw() for pre-rendered HTML content - Support for conditional and list rendering - HTML template literals automatically join arrays of strings, so there is no need to use .join("") - Default to Tailwind CSS classes for styling unless it makes more sense to be CSS in a style tag. - If you need to use CSS, place it in the head: property of the Sapling Layout component - Use the @dark: prefix for dark mode - ALWAYS be sure to escape any backticks inside of html\`\` template literals - IMPORTANT: When using html\`\` template literals inside of \`, // the bodyClass property is strictly a string for the class attribute of the body tag bodyClass: "bg-white text-gray-900 @dark:bg-black @dark:text-white transition-colors duration-200", // the children property is for all of the content to be rendered inside the body tag children: html\`
\${HelloWorldComponent()}

This was server rendered at \${time}

Learn More
\`, }) ); }); Deno.serve(site.fetch); Documentation: - https://sapling.land/docs/llms.txt - https://sapling.land/docs ``` --- title: Site Modules description: Learn how to create and use site modules in Sapling publishedAt: "2024-01-01" --- # Site Modules Site modules are a powerful feature in Sapling that allows you to package and distribute entire websites as reusable modules. This approach enables a clear separation between the site's structure/functionality and its content, making it easier to create templated solutions that others can easily customize. Another great use case for site modules is for web design agencies to create templates that clients can easily customize but the agency can maintain the underlying structure and components under one single module and codebase. ## How Site Modules Work A site module typically contains all the components, layouts, and routing logic of your website, while allowing the end user to simply provide the content. This is particularly useful for creating reusable website templates, documentation sites, a reusable APIs, or any other type of website where you want to maintain a consistent structure but allow for custom content. ## Creating a Module To create a site module, you'll need to: 1. Build your site structure with Sapling 2. Export the necessary configuration functions 3. Publish your module to a registry (like JSR) Here's a basic example of how to structure a site module: ```ts import { Sapling, Layout, html, type HtmlContent } from "@sapling/sapling"; export const site: Sapling = new Sapling(); // Define the content types for the site module export interface LandingLayoutContent { siteTitle: string; hero: { title: string; subtitle: string; } } // Add a configuration option for the content. // This could also be something like the configuration for a CMS. // That way the end user only has to provide an API key. let siteContent: LandingLayoutContent; export function configureSite(content: LandingLayoutContent) { siteContent = content; } site.get("/", async (c) => { if (!siteContent) { throw new Error("Site content not configured. Call configureSite() before starting the server."); } return c.html( await Layout({ unoConfig: config, head: html` ${siteContent.siteTitle} `, bodyClass: "text-gray-900 font-sans min-h-screen @dark:bg-gray-900 @dark:text-white", children: html`

${siteContent.hero.title}

${siteContent.hero.subtitle}

`, })); }); ``` ## Publishing a Module Technically, you could publish your site module to any registry that supports ESM modules. However, we recommend using [JSR](https://jsr.io) because it's free, easy, and has a much better developer experience than NPM. Follow the documentation from [JSR](https://jsr.io/docs/introduction) to publish your site module. However, it should be pretty much as simple as running `deno publish` from the root of your Sapling site. ## Using a Module And then the end user can import and configure the site module like this: ```ts import { site, configureSite } from "jsr:@sapling/site-0"; const content = { siteTitle: "My Site", hero: { title: "Hello, world!", subtitle: "Welcome to my site.", }, }; // Configure the site with the content configureSite(content); Deno.serve(site.fetch); ``` ## Caveats The only caveat to keep in mind is any static files you are serving from a `static` directory on your Sapling site will not be included in the site module. There are three ways to work around this: 1. Host your files elsewhere such as in a CMS, CDN, etc. And then just use the URLs to the files directly in your site module. **(recommended)** 2. Expect your end users to provide their own URLs for static files. This would be if you don't want to incur the cost of hosting the files yourself. 3. Provide the source files to your end users for them to host themselves which is obviously the most cumbersome but still works. This is a null point if your site module is primarily designed to hook into a specific CMS or other system because the CMS is likely handling all of the file hosting. ## Conclusion And yes, that's really all there is to it. Your site module can be as complex or as simple as you'd like. And because of TypeScript, it's fully typed and you'll have autocompletion for the content you provide.