{"id":50742961,"url":"https://github.com/bytebury/bibby","last_synced_at":"2026-06-10T18:30:35.541Z","repository":{"id":360347581,"uuid":"1247922162","full_name":"bytebury/bibby","owner":"bytebury","description":"A full stack framework at the heart of every Bytebury application. Written in Rust. Built with axum, sqlx, htmx, and askama. ","archived":false,"fork":false,"pushed_at":"2026-05-26T02:33:52.000Z","size":68,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-26T04:17:09.004Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bytebury.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-24T00:44:26.000Z","updated_at":"2026-05-26T02:33:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bytebury/bibby","commit_stats":null,"previous_names":["bytebury/bibby"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/bytebury/bibby","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bytebury%2Fbibby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bytebury%2Fbibby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bytebury%2Fbibby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bytebury%2Fbibby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bytebury","download_url":"https://codeload.github.com/bytebury/bibby/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bytebury%2Fbibby/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34165482,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-10T18:30:34.782Z","updated_at":"2026-06-10T18:30:35.532Z","avatar_url":"https://github.com/bytebury.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=center\u003e\n\n\u003cimg width=\"168\" height=\"168\" src=\"https://bytebury.com/assets/images/bibby_welcome.webp\" alt=\"Bytebury's mascot, Bibby\" /\u003e\n\n\u003cp\u003e\n  A full stack template at the heart of every Bytebury application. Written in Rust. Built with axum, sqlx, htmx, tailwindcss, and askama. Out-of-the-box integration with Stripe, OAuth, Postgres, Geolocation, Mailers, Blogs, Announcements, User Management, Privacy and Terms of Service, and more.\n\u003c/p\u003e\n\u003c/div\u003e\n\n# Getting Started\nYou can get started by creating a repository from this template repository. This framework helps the bytebury team deliver efficiently and safely. You will need Rust and NodeJS available in your development environment to run the application.\n\nFor a quick start, you can copy the following lines into your terminal and go to `localhost:8080` to view the application running locally in watch mode.\n\n```sh\ngit clone https://github.com/bytebury/bibby\ncd ./bibby\n./dev.sh\n```\n\nYou can always run the application locally by running the `./dev.sh` file. This will run the application in with a debug build and watch for file changes. This will also watch for html and css changes so that tailwind will also generate.\n\n`./dev.sh` enables the repo's pre-commit hooks automatically for Git checkouts. If you need to enable them manually, run:\n\n```sh\ngit config core.hooksPath .githooks\n```\n\nThe pre-commit hook runs `cargo fmt --all` and `cargo sqlx prepare`. If either command changes files, the commit stops so you can review and stage the generated changes before committing again.\n\n## Design Decisions\nThis framework follows Clean Architecture relatively close. We do not use repositories, as all of our data gathering happens directly on the model itself. This allows for more ergonomic code as seen below.\n\n```rs\npub async fn execute(\u0026self, request: \u0026CreateUser) -\u003e Result\u003cUser\u003e {\n  match User::find_by_email(self.db.as_ref(), \u0026request.email).await {\n      Ok(user) =\u003e Ok(user),\n      Err(_) =\u003e User::create(self.db.as_ref(), \u0026request).await,\n  }\n}\n```\n\nTherefore, we use `use_cases` to derive business logic.\n\n## Common Utilities\n\n### SEO and Web App Metadata\nBibby renders SEO, social sharing, and installable web-app metadata from `SharedContext` in `templates/_layouts/base.html`. The defaults are configured with environment variables, so a new app can ship with correct canonical URLs, Open Graph tags, Twitter cards, a web manifest, and `robots.txt`/`sitemap.xml` without editing templates.\n\nThe important defaults are:\n\n```sh\n# Public URL used for canonical URLs, Open Graph URLs, robots.txt, and sitemap.xml.\n# Falls back to APP_ORIGIN, then http://localhost:${PORT}.\nPUBLIC_SITE_URL=https://example.com\n\nSEO_DEFAULT_TITLE=\"Example App\"\nSEO_DEFAULT_DESCRIPTION=\"A short description of the app.\"\nSEO_DEFAULT_IMAGE=/assets/images/app-icon.svg\nSEO_TWITTER_HANDLE=@example\nSEO_ROBOTS=index,follow\n\nWEB_APP_NAME=\"Example App\"\nWEB_APP_SHORT_NAME=Example\nWEB_APP_THEME_COLOR=\"#111827\"\nWEB_APP_BACKGROUND_COLOR=\"#f9fafb\"\nWEB_APP_DISPLAY=standalone\n\n# Off by default to avoid stale caching surprises in server-rendered HTMX apps.\nWEB_APP_SERVICE_WORKER=false\n```\n\nRoot metadata endpoints are built in:\n\n- `/robots.txt`\n- `/sitemap.xml`\n- `/site.webmanifest`\n- `/service-worker.js`\n\nFor page-specific metadata, set it in the handler when constructing `SharedContext`:\n\n```rs\nuse crate::infra::seo::PageMeta;\n\nshared: SharedContext::new(\u0026state)\n    .with_user(user)\n    .with_canonical_path(\"/pricing\")\n    .with_meta(\n        PageMeta::new()\n            .title(\"Pricing\")\n            .description(\"Choose the plan that fits your team.\"),\n    )\n```\n\nFor pages that should not be indexed, such as admin screens, auth interstitials, and account billing pages, use:\n\n```rs\nshared: SharedContext::new(\u0026state)\n    .with_user(Some(user))\n    .with_canonical_path(\"/users\")\n    .with_meta(PageMeta::new().title(\"Manage Users\").robots(\"noindex,nofollow\"))\n```\n\nBlog posts use `PageMeta::article(\u0026blog)`, which sets article structured data, title, excerpt description, image, published date, updated date, and the canonical slug URL. If you add new public content types, add a model method for sitemap data and include those URLs in `sitemap_xml` in `src/infra/api/core.rs`.\n\n### Pagination\nYou can use the Paginate trait to implement pagination on models.\n\n```rs\nimpl Paginate for User {\n    fn table_name() -\u003e \u0026'static str {\n        \"users\"\n    }\n}\n```\n\n**Sample Usage**\n```rs\npaginate!(User, \u0026db);\npaginate_with!(User, \u0026db, \"where role = $1\", vec![\"admin\"]);\n```\n\n### Redirects\n\nIt is strongly encouraged to use the provided `redirect!` macro when dealing with redirects. Standard browser requests receive a typical 303 Redirect.\nHTMX requests receive a 200 OK with the `HX-Redirect` header, preventing HTMX from swapping the redirect target into the current element and forcing a full-page navigation instead.\n\n**Sample Usage**\n```rs\nasync fn sample(headers: HeaderMap) -\u003e impl IntoResponse {\n    redirect!(\"/\", \u0026headers)\n}\n```\n\n## Web Utilities\n\nThese are the small, framework-agnostic UI helpers that ship with bibby. They are all driven by plain CSS + a tiny script and are wired into the base layout (`templates/_layouts/base.html`), so you don't have to import anything per-page — just use the markup conventions below.\n\n### Spinners\nEvery HTMX-driven button gets an in-flight loading spinner for free. There is no JS to call and no class to add: the CSS in `public/styles/tailwind.css` targets the `htmx-request` class that HTMX itself toggles while a request is in flight, transparentizes the label, and paints a CSS-only spinner via `::after`.\n\nA 150ms grace window means quick requests never flash a spinner — only requests that take longer than 150ms render one. The spinner color is per-variant (`--spinner-color`), so contrast holds on every button class.\n\n**Sample Usage**\n```html\n\u003c!-- Plain HTMX button — spinner is automatic --\u003e\n\u003cbutton hx-post=\"/users/123/promote\" hx-swap=\"none\" class=\"btn-primary\"\u003e\n  Promote\n\u003c/button\u003e\n\n\u003c!-- Form submit — `htmx-request` lands on the \u003cform\u003e, the submit button gets the spinner --\u003e\n\u003cform hx-post=\"/users\" hx-swap=\"none\"\u003e\n  \u003cbutton type=\"submit\" class=\"btn-primary\"\u003eCreate\u003c/button\u003e\n\u003c/form\u003e\n```\n\nFor the common HTMX button case, prefer the `spinner_button` macro in `templates/_macros/buttons.html`, which bundles the standard HTMX attribute set:\n\n```html\n{% import \"_macros/buttons.html\" as ui %}\n\n{% call ui::spinner_button(\"post\", \"/users/123/promote\", \"Promote\") %}{% endcall %}\n{% call ui::spinner_button(\"delete\", url, \"Delete user\", \"btn-danger\", confirm_text) %}{% endcall %}\n```\n\n### Tooltips\nAdd `data-tooltip=\"...\"` to any element and `public/scripts/tooltip.js` will position a tooltip below it on hover. The tooltip is re-initialized after every `htmx:afterSwap`, so it works on swapped-in content too.\n\nThe base layout already renders the required `\u003cdiv id=\"tooltip\" class=\"tooltip\"\u003e\u003c/div\u003e` element — don't remove it. On mobile (`max-width: 748px`) you can opt-out with `data-tooltip-no-mobile`, which is useful for tap targets where a hover tooltip just gets in the way.\n\n**Sample Usage**\n```html\n\u003c!-- Basic tooltip --\u003e\n\u003cimg\n  src=\"/assets/images/flags/us.svg\"\n  data-tooltip=\"United States\"\n  class=\"h-4 w-5\"\n/\u003e\n\n\u003c!-- Suppress on mobile --\u003e\n\u003cbutton data-tooltip=\"Delete this user\" data-tooltip-no-mobile class=\"btn-ghost\"\u003e\n  \u0026times;\n\u003c/button\u003e\n```\n\nTemplates already do this for the flag overlay on user avatars (see `templates/users/_edit_user.html`).\n\n### Modals\nModals are HTMX-driven: any button that targets `#modal` loads a partial into the modal slot, and `public/scripts/modal.js` flips the wrapper to `display: flex` on `htmx:afterSwap`. Closing is handled by `closeModal()` (the underlay click, an `\u0026times;` button, the `Escape` key, or a server-emitted `closeModal` event all work).\n\nThe base layout already renders the required wrapper:\n\n```html\n\u003cdiv id=\"modal_wrapper\" class=\"modal wrapper\" style=\"display: none\"\u003e\n  \u003cdiv class=\"underlay\" onclick=\"closeModal()\"\u003e\u003c/div\u003e\n  \u003cdiv id=\"modal\" class=\"content\"\u003e\u003c/div\u003e\n\u003c/div\u003e\n```\n\nModal partials should be **content-only** — just the inner card markup, no wrapper. Use `onclick=\"closeModal()\"` for the close button.\n\n**Sample Usage**\n```html\n\u003c!-- Trigger: any button that swaps a partial into #modal --\u003e\n\u003cbutton hx-get=\"/users/123/edit\" hx-target=\"#modal\" class=\"btn-link\"\u003e\n  Edit\n\u003c/button\u003e\n\n\u003c!-- Partial returned by /users/123/edit (content-only) --\u003e\n\u003cdiv class=\"card-header\"\u003e\n  \u003ch2 class=\"card-title\"\u003eEdit user\u003c/h2\u003e\n  \u003cbutton type=\"button\" class=\"btn-ghost\" onclick=\"closeModal()\"\u003e\u0026times;\u003c/button\u003e\n\u003c/div\u003e\n\u003cform hx-patch=\"/users/123\" hx-swap=\"none\" class=\"card-body\"\u003e\n  \u003c!-- ...fields... --\u003e\n\u003c/form\u003e\n```\n\nTo close the modal from the server after a successful action, emit the `closeModal` event via `HX-Trigger`:\n\n```rs\n([(\"HX-Trigger\", \"closeModal\")], \"\").into_response()\n```\n\n### Popover Menus\nPopover menus use the native `\u003cdetails\u003e` element with the `menu` class — no extra state to manage. `public/scripts/menu.js` adds two behaviors on top: clicking outside the open menu closes it, clicking an item inside `.menu-items` closes it, and `Escape` closes all open menus. Because it's just a `\u003cdetails\u003e`, keyboard activation and screen reader semantics come for free.\n\n**Sample Usage**\n```html\n\u003cdetails class=\"menu relative\"\u003e\n  \u003csummary class=\"btn-ghost cursor-pointer list-none\"\u003e\n    Actions\n  \u003c/summary\u003e\n  \u003cdiv class=\"menu-items absolute right-0 mt-1 w-44 rounded-md border border-gray-200 bg-white shadow-lg\"\u003e\n    \u003cbutton hx-get=\"/users/123/edit\" hx-target=\"#modal\" class=\"block w-full px-3 py-2 text-left text-sm hover:bg-gray-50\"\u003e\n      Edit\n    \u003c/button\u003e\n    \u003cbutton hx-delete=\"/users/123\" hx-confirm=\"Delete this user?\" class=\"block w-full px-3 py-2 text-left text-sm text-red-600 hover:bg-gray-50\"\u003e\n      Delete\n    \u003c/button\u003e\n  \u003c/div\u003e\n\u003c/details\u003e\n```\n\nNote: the tooltip script automatically suppresses hover tooltips on `details.menu \u003e summary` so a tooltip doesn't pop while the menu is opening.\n\n## OAuth Providers\nBy default, we support Google OAuth out of the box. If you'd like to support other OAuth clients, you will need to add your new provider into the `OAuthProvider` enum and add a configuration for it. This will automatically set up the auth endpoints `/auth/{provider_code}` and the callback `/auth/{provider_code}/callback`.\n\n## Opt-out Microservices\nBytebury provides a few libraries and microservices that bibby can incorporate into projects. By default these are all included. You can delete any you feel are not important to your needs. Bytebury uses Railway as our primary cloud host, so you may see some preference there, but all microservices are platform / cloud-provider agnostic.\n\n| Name | Description |\n| --- | --- |\n| geodude | A geolocation microservice built on top of ip2location. Supports auto-updates from ip2location. |\n| paperboy | A mailer microservice. Send e-mails on your behalf. |\n\n## Listening to Stripe Webhooks\n```sh\nstripe listen --forward-to localhost:8080/stripe\n```\n\n# Tests\nWe strongly encourage you to test mission-critical features through end-to-end (e2e) tests. Bibby uses Playwright for this, which can be run using `./e2e.sh`. We also bootstrap GitHub Actions, which will automatically execute tests and e2e tests on every Pull Request as the default functionality.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbytebury%2Fbibby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbytebury%2Fbibby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbytebury%2Fbibby/lists"}