An open API service indexing awesome lists of open source software.

https://github.com/bytebury/bibby

A full stack framework at the heart of every Bytebury application. Written in Rust. Built with axum, sqlx, htmx, and askama.
https://github.com/bytebury/bibby

Last synced: about 1 month ago
JSON representation

A full stack framework at the heart of every Bytebury application. Written in Rust. Built with axum, sqlx, htmx, and askama.

Awesome Lists containing this project

README

          

Bytebury's mascot, Bibby


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.


# Getting Started
You 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.

For 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.

```sh
git clone https://github.com/bytebury/bibby
cd ./bibby
./dev.sh
```

You 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.

`./dev.sh` enables the repo's pre-commit hooks automatically for Git checkouts. If you need to enable them manually, run:

```sh
git config core.hooksPath .githooks
```

The 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.

## Design Decisions
This 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.

```rs
pub async fn execute(&self, request: &CreateUser) -> Result {
match User::find_by_email(self.db.as_ref(), &request.email).await {
Ok(user) => Ok(user),
Err(_) => User::create(self.db.as_ref(), &request).await,
}
}
```

Therefore, we use `use_cases` to derive business logic.

## Common Utilities

### SEO and Web App Metadata
Bibby 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.

The important defaults are:

```sh
# Public URL used for canonical URLs, Open Graph URLs, robots.txt, and sitemap.xml.
# Falls back to APP_ORIGIN, then http://localhost:${PORT}.
PUBLIC_SITE_URL=https://example.com

SEO_DEFAULT_TITLE="Example App"
SEO_DEFAULT_DESCRIPTION="A short description of the app."
SEO_DEFAULT_IMAGE=/assets/images/app-icon.svg
SEO_TWITTER_HANDLE=@example
SEO_ROBOTS=index,follow

WEB_APP_NAME="Example App"
WEB_APP_SHORT_NAME=Example
WEB_APP_THEME_COLOR="#111827"
WEB_APP_BACKGROUND_COLOR="#f9fafb"
WEB_APP_DISPLAY=standalone

# Off by default to avoid stale caching surprises in server-rendered HTMX apps.
WEB_APP_SERVICE_WORKER=false
```

Root metadata endpoints are built in:

- `/robots.txt`
- `/sitemap.xml`
- `/site.webmanifest`
- `/service-worker.js`

For page-specific metadata, set it in the handler when constructing `SharedContext`:

```rs
use crate::infra::seo::PageMeta;

shared: SharedContext::new(&state)
.with_user(user)
.with_canonical_path("/pricing")
.with_meta(
PageMeta::new()
.title("Pricing")
.description("Choose the plan that fits your team."),
)
```

For pages that should not be indexed, such as admin screens, auth interstitials, and account billing pages, use:

```rs
shared: SharedContext::new(&state)
.with_user(Some(user))
.with_canonical_path("/users")
.with_meta(PageMeta::new().title("Manage Users").robots("noindex,nofollow"))
```

Blog posts use `PageMeta::article(&blog)`, 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`.

### Pagination
You can use the Paginate trait to implement pagination on models.

```rs
impl Paginate for User {
fn table_name() -> &'static str {
"users"
}
}
```

**Sample Usage**
```rs
paginate!(User, &db);
paginate_with!(User, &db, "where role = $1", vec!["admin"]);
```

### Redirects

It is strongly encouraged to use the provided `redirect!` macro when dealing with redirects. Standard browser requests receive a typical 303 Redirect.
HTMX 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.

**Sample Usage**
```rs
async fn sample(headers: HeaderMap) -> impl IntoResponse {
redirect!("/", &headers)
}
```

## Web Utilities

These 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.

### Spinners
Every 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`.

A 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.

**Sample Usage**
```html

Promote

Create

```

For the common HTMX button case, prefer the `spinner_button` macro in `templates/_macros/buttons.html`, which bundles the standard HTMX attribute set:

```html
{% import "_macros/buttons.html" as ui %}

{% call ui::spinner_button("post", "/users/123/promote", "Promote") %}{% endcall %}
{% call ui::spinner_button("delete", url, "Delete user", "btn-danger", confirm_text) %}{% endcall %}
```

### Tooltips
Add `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.

The base layout already renders the required `

` 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.

**Sample Usage**
```html

×

```

Templates already do this for the flag overlay on user avatars (see `templates/users/_edit_user.html`).

### Modals
Modals 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 `×` button, the `Escape` key, or a server-emitted `closeModal` event all work).

The base layout already renders the required wrapper:

```html


```

Modal partials should be **content-only** — just the inner card markup, no wrapper. Use `onclick="closeModal()"` for the close button.

**Sample Usage**
```html

Edit


Edit user


×

```

To close the modal from the server after a successful action, emit the `closeModal` event via `HX-Trigger`:

```rs
([("HX-Trigger", "closeModal")], "").into_response()
```

### Popover Menus
Popover menus use the native `` 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 ``, keyboard activation and screen reader semantics come for free.

**Sample Usage**
```html


Actions

```

Note: the tooltip script automatically suppresses hover tooltips on `details.menu > summary` so a tooltip doesn't pop while the menu is opening.

## OAuth Providers
By 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`.

## Opt-out Microservices
Bytebury 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.

| Name | Description |
| --- | --- |
| geodude | A geolocation microservice built on top of ip2location. Supports auto-updates from ip2location. |
| paperboy | A mailer microservice. Send e-mails on your behalf. |

## Listening to Stripe Webhooks
```sh
stripe listen --forward-to localhost:8080/stripe
```

# Tests
We 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.